• 第03篇_Spring_Core

    第00章_前言

    Spring是Java EE编程领域的一个轻量级开源框架,最早由Rod Johnson在 2002 年提出并随后创建,是为了解决企业级编程开发中的复杂性,实现敏捷开发的应用型框架 。框架的构成如下图所示,核心的模块有控制反转容器、面向切面编程、Web开发支持等。

    image-20210803223825285

    Spring与传统的EJB相比,具有下面一些优势。

    第01章_IOC容器

    第一节 程序中的耦合

    1. 程序中的耦合

    程序中的耦合指的是代码之间的相互依赖关系。如下面一段代码,业务层需要调用持久层进行保存操作,此时业务层就对持久层产生了直接依赖,如果需要对持久层实现类进行替换,需要修改业务层代码,不符合开闭原则。

     

     

    2. 使用工厂模式来解决耦合

    首先将持久层对象的创建交给一个工厂类来实现,那么依赖关系将由服务层->持久层转变为服务层->工厂类。然后在工厂类通过反射来创建我们所需的对象,并且可以将全类名放置在外部配置文件中,以此来解决代码中存在的直接依赖关系。

    现在对持久层实现类进行动态替换时,只需要修改外部配置文件就可以了,上述这种将业务层对持久层的依赖转移给工厂的解决思路,被称为控制反转(Inversion of Control)。

    控制反转是一个概念,是一种思想,指将传统上由程序代码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。控制反转就是对象控制权的转移,从程序代码本身反转到了外部容器,通过容器实现对象的创建,属性赋值,依赖的管理。

     

     

    3. 使用Spring解决程序耦合

    程序中的耦合是一个普遍存在问题,我们没必要为每一个应用都重复的编写上述类似代码,业界已经提供了一个通用的,被大众所认可的优秀解决方案Spring,让我们来看看如何使用Spring来解决程序中的耦合问题。

     

    导入Spring依赖

    注意:一般我们会选择较新的Spring 5 版本,这需要你的JDK版本在1.8及以上,Tomcat版本在8.0及以上。

     

    配置IOC容器

    需要一个配置文件用来配置 key 和全类名的映射关系,这个文件名一般为application.xml,放在任意类路径下即可。

     

    使用IOC容器

    在业务代码中通过容器来获取所需的依赖,而不是直接创建依赖对象。

     

     

     

    第二节 IOC容器

    1. ApplicationContext接口

    ApplicationContext接口继承自 BeanFactory,代表 Spring 的 IOC 容器,容器读取配置元数据中的指令,进行对象的实例化、配置和组装。Spring为适应不同的场景,提供了多个实现类,常见的几种如下:

    实现类说明
    ClassPathXmlApplicationContext从类路径下加载配置文件,独立应用程序一般使用此种方式
    FileSystemXmlApplicationContext从磁盘位置加载配置文件,受限于操作系统,一般不常用
    AnnotationConfigApplicationContext从Java代码加载配置文件,一般适用于基于Java配置的注解开发模式
    XmlWebApplicationContext适用于Web应用的容器实现类,只有引入了web相关的依赖才能使用

    ApplicationContext和BeanFactory的区别

    BeanFactory 提供了最基本的容器功能,而 ApplicationContext 是 BeanFactory 的完整超集,添加了更多的企业特定的功能。

    • 面向切面编程(AOP)

    • Web应用(WebApplicationContext)

    • 国际化(MessageSource)

    • 事件传递(Event publication)

    特别的,BeanFactory 总是会懒初始化 Bean,而 ApplicationContext 默认情况下在容器启动时初始化所有的Singleton Bean。

     

     

    2. 容器的创建

    容器的创建需要提供配置元数据,这些元数据可以是XML注解Java代码的形式。

     

    XML配置方式

     

    注解配置方式

    SpringConfiguration 的代码如下,一般为一个配置类(@Configuration注解的类)。

    当然,也可以使用普通的组件类(@Component注解的类)或带有 JSR-330 元数据注解的类。

     

    编程配置方式

     

     

    3. 容器的关闭

    在Web环境中,Spring 的基于 Web 的容器实现已经具有适当的代码,可以在相关 Web 应用程序关闭时正常关闭 Spring IoC 容器。在非 Web 应用程序环境中,请向 JVM 注册一个关闭钩子,这样Spring可以调用你单例 bean 上设置的 destroy 方法,以便释放所有资源。

    JVM的关闭钩子在什么场景下有效?

    答:正常退出、System.exit()、Ctrl+C中断、OutofMemory宕机、Kill pid杀死进程(kill -9除外)、系统关闭等。

     

     

    3. 容器的使用

    容器创建完成后,我们就可以通过使用方法T getBean(String name, Class<T> requiredType)检索 bean 的实例。

    除此之外,ApplicationContext接口还有其他几种检索 bean 的方法,但是理想情况下,您的应用程序代码永远不要使用它们。

     

    容器除了获取对象的方法外,还提供了一些辅助方法,用于获取容器的相关信息。

     

    4. 容器的扩展

    BeanPostProcessor

    BeanPostProcessor 接口可以在 Bean 的初始化生命周期前后植入一些自定义逻辑,对创建的 Bean 进行二次加工。

     

    BeanFactoryPostProcessor

    BeanFactoryPostProcessor 接口用于读取配置元数据,并在Bean的实例化之前对其进行修改。Spring定义了一些内置的Bean工厂后处理器,如PropertyPlaceholderConfigurer用于解析配置文件中的占位符,并进行字符串替换操作。

    除此之外,与属性相关的Bean工厂后处理器还有 PropertyOverrideConfigurer,用于对配置的属性进行覆盖操作。

    一个 override.properties 配置文件的示例如下。

     

    Ban(Factory)PostProcessor 的一些处理细节

    1. Bean(Factory)PostProcessor 是容器范围的,对该容器内的所有 Bean 都生效。

    2. Bean(Factory)PostProcessor 会尽早被实例化,即使你为它们配置了懒加载属性,容器也会忽略。

    3. 你定义了多个 Bean(Factory)PostProcessor,可以通过实现Ordered接口并使用order属性来控制它们的执行顺序。

    4. 如果你使用@Bean来创建Bean(Factory)PostProcessor,则必须保证返回类型是该接口的实现类,否则容器无法正确检测。

    5. AOP 中一些基础结构类是基于BeanPostProcessor来实现的,因此该接口的实现类和依赖其的Bean都不适合自动代理。

    6. BeanFactoryPostProcessor 不能对实现了BeanFactoryPostProcessor接口的Bean定义进行修改。

    7. 虽然可以通过BeanFactory.getBean()来提早实例化Bean,但这是不被推荐的,因为可以会绕过 bean 的后处理。

     

     

     

    5. 多文件配置

    引入外部配置文件

    你也可以将所有元数据写在同一个配置文件中,或者通过<import/>标签引入其它文件中的元数据配置。

     

    引入外部属性文件

    对于一些需要经常修改的内容,如Jdbc连接信息等,可以单独放在一个小配置文件中,方便维护。

    db.properties文件示例如下:

     

     

     

    第三节 Bean详解

    1. Bean的实例化

    构造方法创建

    Spring配置文件的顶层标签为beans,可以配置多个<bean>标签,一个简单的bean配置示例如下:

    其中id属性是全局的唯一标识,用于从容器中获取对象。class属性则是对应的全限定类名,默认情况下,使用反射调用类中的无参构造函数来创建对象,如果缺少无参构造则无法创建。

     

     

    FactoryBean创建

    随着业务的复杂性提升,某些复杂对象的创建不能直接通过new关键字来实现,如ConnectSqlSessionFactory等,为此Spring提供了实现FactoryBean接口的方式来完成复杂对象的配置,首先实现FactoryBean接口,示例如下。 image-20200416204458451

    然后在配置文件中引用FactoryBean实现类来创建所需要的对象,配置如下:

    值得注意的是,这和简单对象的配置很相似,但实际上,Spring会检测class属性配置的类,如果是FactoryBean接口的实现类,那么通过id值获得的是这个类所创建的复杂对象(如:Connection)。

    提示:就想获得FactoryBean类型的对象,可以通过ctx.getBean("&conn")的方式获取,得到的就是ConnectionFactoryBean对象。

     

     

    普通工厂类来创建

    虽然上述方式解决了复杂对象创建的问题,但使用的工厂类必须实现FacotryBean接口,为了整合一些遗留系统,Spring还可以使用普通的工厂类来创建所需的对象。

     

    这种方式也是在class属性指定使用的工厂类,但需要通过factory-method属性额外指定创建对象的静态方法,示例如下:

     

    如果factory-method是非静态方法,那么将class属性留空,使用factory-bean属性引用工厂类对象,示例如下:

     

     

    2. Bean的生命周期

    生命周期指Bean从创建到销毁的整个过程,Spring提供了一些机制允许我们对bean生命周期的管理进行干预。

     

    @PostConstruct /@PreDestroy注解

    @PostConstruct@PreDestroy 是 JSR-250 的生命周期注解,Spring 在 CommonAnnotationBeanPostProcessor 中对其做了实现。在 Bean 的初始化期间会调用被@PostConstruct注解的方法,在销毁期间会调用被@PreDestroy注解的方法。

     

    InitializingBean/DisposableBean接口

    如果bean实现了InitializingBean接口,容器会在创建bean的时候调用其afterPropertiesSet()方法,如果实现了DisposableBean接口,容器在销毁bean的时候调用其destroy()方法。

     

    init-method/destroy-method属性

    对于基于 XML 的配置元数据,可以使用init-method/destroy-method属性指定无参无返回值方法的名称作为初始化方法和销毁方法。

    为了简化配置,你可以使用<beans>标签的default-init-method/default-destroy-method属性来为所有子标签中的bean指定默认初始化和销毁方法。但必须注意,Spring 容器保证在为 bean 提供所有依赖项后立即调用配置的初始化回调,因此,在原始 bean 引用上调用了初始化回调,这意味着 AOP 拦截器等尚未应用于 bean。

    提示:使用Java代码配置元数据时,使用@Bean的initMethoddestroyMethod属性来指定初始化和销毁方法。

     

    Bean启动和停止事件回调

    可以通过实现Lifecycle接口来定义 Bean 的启动和停止事件回调,当容器本身接收到启动和停止 signal 时(例如,对于运行时的停止/重新启动场景),它将把这些调用级联到在该上下文中定义的所有Lifecycle实现。

    如果想对 start 和 stop 生命周期进行更精细的控制,可以实现SmartLifecycle接口。如通过isAutoStartup方法返回 true 可以实现容器创建后自动启动;通过getPhase()方法返回合适的相位值来控制启动和停止的顺序,该值越大,则越晚执行,越早销毁,默认值为Integer.MAX_VALUE(如果实现的是LifeCycle接口,则默认为0),详细用法请参考Spring官方文档。

     

     

    不同生命周期机制的执行顺序

    如果为一个 bean 配置了多个生命周期机制,当方法名称相同时,该方法将只执行一次。如果方法名称不一致,那么将按照注解配置优先,接口配置次之,XML配置最后执行的原则调用生命周期方法。

    1)用 @PostConstruct 注解的方法

    2)InitializingBean 回调接口定义的 afterPropertiesSet()方法

    3)自定义配置的 init() 方法

    4)用 @PreDestroy 注解的方法

    5)DisposableBean 回调接口定义的 destroy()方法

    6)自定义配置的 destroy() 方法

     

     

    3. Bean的作用范围

    Bean的作用范围决定了Bean的作用域以及在获取对象时是否创建新的实例等,通过scope属性进行配置。

     

    singleton

    默认配置的作用范围为singleton,表示单例范围,只创建一次,一般在容器启动时创建,容器销毁时随之销毁。

     

    prototype

    prototype表示原型范围(多例),在每次从容器获取对象时,始终会创建新的实例返回。

    与其它作用范围不同的是,Spring不管理原型Bean的完整生命周期,不会调用已配置的生命周期销毁回调。

    注意关于单例bean中注入原型bean的一些细节

    • 在单例Bean中依赖原型Bean时,由于单例Bean只会被IoC容器初始化一次,其依赖也只会被处理一次,因此其依赖的原型Bean也将“隐式”的成为单例。

    • 如何解决这个问题,有两种办法,一种是在使用原型Bean时每次都依赖查找,这样IoC容器会每次都重新创建原型Bean;

    • 另一种办法就是使用@Lookup注解来解决,这种是官方给出解决方案,需注意的是使用@Lookup注解的方法必须声明为抽象方法。

     

    适用于web的作用范围

    在Web应用中,提供了一些特殊的bean作用范围,如requestsessionapplicationwebsocket等,分别与HTTP中的Request、Session、ServletContext、WebSocket作用域一致,这里不做详细讲解,具体可参考Spring的官方文档。

     

    自定义作用范围

    Bean 作用域机制是可扩展的,您可以定义自己的范围,甚至重新定义一些内置范围(singleton和prototype除外)。要将自定义范围集成到 Spring 容器中,您需要实现Scope接口。Scope接口有四种方法可以从范围中获取对象,从范围中删除对象,然后销毁它们。

    在编写自定义Scope实现之后,您需要使 Spring 容器意识到您的新作用域。通过以下方法在 Spring 容器中注册新的Scope。

    除了使用程序进行Scope的注册外,您还可以通过使用CustomScopeConfigurer类以声明方式进行Scope注册,如以下示例所示:

    从 Spring 3.0 开始,线程作用域可用,但默认情况下未注册。有关更多信息,请参见SimpleThreadScope的文档。

     

     

    4. Bean的感知接口

    感知接口(Aware)表示 Bean 向容器声明它们需要某种基础结构依赖性。这些接口回调在填充常规 bean 属性之后,但在初始化回调(例如InitializingBean,afterPropertiesSet或自定义 init-method)之前调用。

     

    ApplicationContextAware

    当 ApplicationContext 在创建实现ApplicationContextAware接口的对象实例时,该实例将获得对该ApplicationContext的引用。

     

    BeanNameAware

    当 ApplicationContext 在创建实现BeanNameAware接口的类时,该类将获得对在其关联的对象定义中定义的名称的引用。

     

    其它感知接口
    NameInjected Dependency
    ApplicationEventPublisherAwareApplicationContext的事件发布者
    BeanFactoryAwareBeanFactory
    BeanClassLoaderAware类加载器(用于加载 Bean 类)
    LoadTimeWeaverAware编织器(用于在加载时处理类定义)
    MessageSourceAware解析消息的配置策略(支持参数化和国际化)
    NotificationPublisherAwareSpring JMX 通知发布者
    ResourceLoaderAware资源加载器(用于低级资源访问)
    ServletConfigAware当前容器运行的ServletConfig(仅在Web环境有效)
    ServletContextAware当前容器运行的ServletContext(仅在Web环境有效)

    注意:使用这些接口会将您的代码与 Spring API 绑定在一起,并且不遵循“控制反转”样式。因此,我们建议将它们用于需要以编程方式访问容器的基础结构 Bean。

     

     

    5. Bean的自动装配

    Spring 容器可以自动装配协作 Bean 之间的关系,即在创建对象后,自动从容器中查找所需的依赖并进行注入。这可以大大减少指定属性或构造函数参数的需要,并且当类中新增依赖项时,无需修改配置即可自动满足该依赖项。

    自动装配的模式共有四种,通过bean标签的autowire属性来指定。

    ModeExplanation
    no(默认)不进行自动装配, 仅由 ref 属性来定义 Bean 之间的依赖关系,可以提供更好的控制和清晰度。
    byName按属性名称自动装配,Spring通过属性名在容器中查找匹配的依赖 Bean 进行注入。
    byType按属性类型自动装配,如果查找到唯一匹配的依赖Bean,则进行注入。但如果查找到多个,则引发致命异常。
    constructor类似于byType,适用于构造函数参数的自动装配。不同的是,如果未查找到匹配的依赖Bean也会引发致命异常。

    如果使用ByType自动装配匹配到了多个Bean,则可以设置某些Bean的 autowire-candidate属性为false,从候选列表去除。或者也可以通过设置某个Bean的primary属性为true,将其作为主要候选对象。上面两种配置仅对按类型自动装配(byType/Constructor)有效,按名称自动装配(byName)不受其影响。

     

     

    6. Bean定义继承

    在Bean的配置中,可以通过parent属性来指定另一个Bean作为该Bean的 ParentBean, 从而继承一些可复用的配置数据,并可以覆盖某些值或根据需要添加其他值,这是一种模板方法模式的一种体现。

    ChildBean 可以从 ParentBean 继承Bean的作用范围生命周期属性注入等信息,但依赖项自动装配模式等一些信息始终从子类获取,这需要我们在开发过程中多加关注。

    注意:

    1. Spring的继承是对象层面的继承,子类继承父类对象的属性值。因此,Spring 中,不同类之间可以互相继承。

    2. Java是类层面的继承,继承的是父类的类结构信息。如果用 Java 代码的方式来配置元数据,那么可以直接使用 java 的继承机制来复用元数据的配置信息。

     

     

    7. Bean其它属性

    别名(name/alias)

    Bean除了具有唯一性的 id 属性外,还可以定义若干个别名,以兼容不同的业务系统,通常使用name属性或<alias>标签来实现。

     

    初始化顺序(depends-on)

    depends-on属性可以指定初始化时间依赖性,也可以在Bean是单例的的情况下指定相应的销毁时间依赖性。

     

    懒加载(lazy-init)

    单例Bean默认在容器创建时进行实例化,如果想让Bean在使用时再创建,则可指定lazy-init属性为true。

    如果需要对多个Bean进行配置,也可以选择beans标签的default-lazy-init属性,为其内部的所有Bean设置可覆盖的默认值。

    提示:如果你使用注解配置,可以使用 @Lazy 注解来实现懒加载,并且该注解可以放置在标有@Autowired@Inject的注入点上。在这种情况下,它导致注入了惰性解析代理。

     

     

    第四节 依赖注入配置

    依赖注入 (Dependency Inject)是控制反转思想的一种实现方式,指的是程序运行过程中,若需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部容器,由外部容器创建后传递给程序。

     

    1. 构造函数注入

    基于构造函数的注入是通过容器调用有参构造函数来完成的,每个参数代表了一个依赖项。

    对于极少数情况下无法使用构造函数自变量名称的情况(通常,如果字节码是在没有调试信息的情况下编译的),可以通过参数索引进行构造函数的匹配,并且允许在能够推断的情况下省略索引属性。

    还可以使用更为简洁的语法来进行构造函数的注入配置,但这最好是在创建 bean 定义时使用支持自动属性完成的 IDE,否则错字是在运行时而不是设计时发现的。

    提示:使用工厂创建Bean对象时,可以通过constructor-arg标签进行工厂方法的参数注入,使用方式同构造函数注入一致。

     

     

    2. Set方法注入

    基于Set方法的注入是在对象创建完成后,调用对象的set方法来进行属性注入。

    类似的,可以使用p名称空间来进行简化书写。

     

     

    3. 注入集合属性

    顾名思义,就是给类中的集合成员进行属性注入,用的也是set方法注入的方式,只不过变量的数据类型都是集合。

    提示:集合数据分两种,单列集合(array,list,set)和双列集合(map,entry,props,prop),同类型集合注入方式可以兼容。

     

    如果Bean存在继承关系,则可以通过merge属性来合并父类的集合。

     

     

    4. 注入原型Bean

    如果某个Bean是单例的,并且依赖了原型Bean,则该原型Bean不能使用上述方式直接注入,必须在每次使用时都创建新的实例。

     

    实现感知接口方式

    通过实现感知接口ApplicationContextAware或BeanFactoryAware获取容器的引用,进而调用getBean方法创建原型Bean。

     

    使用Lookup方式

    上述方式依赖了Spring框架,并且由程序代码主动获取对象,不符合控制反转的原则,下面将使用XML配置方式由Spring来完成上述代码。

    也可以使用注解方式来配置,并且@Lookup的 value 属性可以根据查找方法声明的返回类型来解析。

    注意:

    1. 查找方法的签名必须是<public|protected> [abstract] <return-type> theMethodName(no-arguments)形式。

    2. 查找方法允许是非 abstract 的,Spring会将原来的方法覆盖。

    3. 由于Spring是通过CGLib来实现该方式的,因此该类和查找方法都不能被final修饰。

    4. 查找方法不适用于工厂方法和配置类中的@Bean方法,因此在这种情况下,实例并不是由Spring创建的,无法进行动态代理。

     

    注入ObjectFactory

     

     

    5. 循环依赖问题

    在进行依赖注入时,如果A依赖B,B依赖C,而C又依赖A,则有可能出现循环依赖问题,抛出BeanCurrentlyInCreationException异常,下面将使用案例对三种不同的注入场景进行分析。

     

    通过构造函数注入

    首先通过构造函数注入,来描述上面所述的依赖关系,在创建容器时抛出了预期的BeanCurrentlyInCreationException异常。

    Spring容器会将每一个正在创建的Bean标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持在这个池中。因此如果在创建Bean过程中发现自己已经在“当前创建Bean池”里时将抛出BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的Bean将从“当前创建Bean池”中清除掉。

    根据上述源码实现分析:Spring容器先创建单例StudentA,StudentA依赖StudentB,然后将A放在“当前创建Bean池”中,此时创建StudentB, StudentB依赖StudentC ,然后将B放在“当前创建Bean池”中,此时创建StudentC,StudentC又依赖StudentA, 但是,此时StudentA已经在池中,所以会报错,因为在池中的Bean都是未初始化完的,所以会依赖错误 ,(初始化完的Bean会从池中移除)。

     

    通过Set方法注入(单例范围)

    再来看下使用Set方法注入,并设置Bean范围为单例范围,发现在创建容器时并未抛出异常。

    为什么使用Set方法不抛异常呢?关键在于Spring先将Bean对象实例化之后再设置对象属性的。Spring先用构造函数实例化Bean对象 ,然后存入到一个Map中,当StudentA、StudentB、StudentC都实例化完成后,然后才去设置对象的属性,此时StudentA依赖StudentB,就会去Map中取出存在里面的单例StudentB对象,以此类推,不会出来循环的问题喽。

     

    通过Set方法注入(原型范围)

    如果Bean的范围为原型,即使使用Set注入,也会抛出BeanCurrentlyInCreationException异常。

    因为“prototype”作用域的Bean,Spring容器不进行三级缓存,因此无法提前暴露一个创建中的Bean。

     

     

    6. 静态变量注入

    我们封装工具类的时候,大多数提供的是静态方法,而静态方法只能访问静态变量,此时,则需要将我们所需的依赖注入到静态变量中。

     

    通过Set方法注入

     

    使用@PostConstruct注解

     

     

    7. 普通类使用Bean

    如果某个类是一个普通的Java,并且并没有被Spring容器所管理,那么如何使用Spring容器创建的Bean实例呢?

     

    SpringUtils工具类

    定义一个工具类,实现容器的感知接口,通过静态方法向外部提供获取Bean的功能。

     

    静态化Bean

    修改 Bean 的代码,在初始化回调时将 Bean 的 this 指针设置到一个静态变量中。

     

     

    8. 自定义类型处理器

    Spring通过类型转换器把配置文件中字符串类型的数据,转换成了对象中成员变量对应类型的数据,进而完成了注入,当Spring内部没有提供特定类型的转换器时,那么就需要程序员自己定义类型转换器。

     

     

     

     

    第五节 基于注解的IOC配置

    Spring从2.x版本开始,提供了注解方式来配置IOC容器和注册Bean, 充分利用程序中提供的上下文,使得配置更加简短和整洁。

    在使用基于XML配置的ClassPathXmlApplicationContext来构建IOC容器时,需要在XML中开启注解配置开关。

    开启注解配置开关会自动注册下面一些后处理器,并且在定义它的应用程序上下文中扫描Bean上的注解。

    1. AutowiredAnnotationBeanPostProcessor

    2. CommonAnnotationBeanPostProcessor

    3. PersistenceAnnotationBeanPostProcessor

    4. RequiredAnnotationBeanPostProcessor

    也可以使用AnnotationConfigApplicationContext直接基于注解配置来构建容器,然后使用@ImportResuorce注解导入XML配置。

    注意

    1. 注解配置对源代码存在侵入,并且配置分散不利于维护,在使用时需结合实际情况选用。

    2. Spring优先对注解配置的属性进行注入,如果在XML中配置了相同的属性,那么将会对之前的配置进行覆盖。

     

     

    1. 使用@Component标记组件

    @Component 注解作用于类、接口、枚举或其它注解之上,标记该元素为Spring的一个组件,唯一的 value 属性用于指定组件的名称。

    为了区分该组件属于持久层、服务层或是控制层,分别为此定义了三个语义性注解:@Repository@Service@Controller。这在将来可能还会有一些特殊的语义,如@Repository被支持作为持久层中自动异常转换的标记等。

     

     

    2. 使用@ComponnetScan进行组件扫描

    @ComponentScan 一般作用于 @Configuration 类上,用于自动检测标记的组件,并将构造型类向ApplicationContext注册相应的BeanDefinition实例。

    等效的XML配置如下。

     

    配置扫描过滤器

    默认情况下,会扫描所有被 @Component 及其衍生注解标记的组件,你可以通过includeFiltersexcludeFilters属性设置过滤器来修改和扩展此行为,过滤器的类型和描述如下列表所示。

    Filter TypeExample ExpressionDescription
    annotation (default)org.example.SomeAnnotation在目标组件的类型级别上存在的注解。
    assignableorg.example.SomeClass目标组件可分配给(扩展或实现)的类(或接口)。
    aspectjorg.example..*Service+目标组件要匹配的 AspectJ 类型表达式。
    regexorg.example.Default.*要与目标组件类名称匹配的正则表达式。
    customorg.example.MyTypeFilterorg.springframework.core.type .TypeFilter 接口的自定义实现。

    等效的XML配置如下。

    也可以参考如下,下面是一个线上项目中使用的案例。

    提示: 你可以将 useDefaultFilters (或 use-default-filters)属性设置为 false 来禁止移除默认过滤器,这将禁用对带有@Component@Repository@Service@Controller@Configuration注解的类的自动检测。

     

    自定义名称生成策略

    在扫描过程中自动检测到某个组件时,其 bean 名称由该扫描器已知的BeanNameGenerator策略生成。默认使用配置的 value 属性值,如果未配置则使用类名的首字母小写作为 bean 名称。

    如果你想对名称生成策略进行修改,可以实现 BeanNameGenerator 接口,提供无参构造函数,然后在 @ComponentScan 注解的 nameGenerator 属性进行配置。

    等效的XML配置如下:

     

    生成组件索引

    在大型应用程序中启动时,组件扫描可能会花费不短的时间,可以通过在编译时创建候选静态列表(索引)来提高大型应用程序的启动性能。

    注意

    1. 如果ApplicationContext在启动时检测到组件索引,则会直接使用索引,而不会再进行组件扫描。这意味着你必须给所有存在组件的模块都加上依赖。

    2. 可以使用全局属性spring.index.ignore=true来忽略组件索引,回退为组件扫描的方式。

     

     

    3. 配置Bean的属性

    使用@Scope配置组件的作用范围

    组件默认注册为 singleton 范围,如果相对其进行修改,可以使用 @Scope 注解。

    注意

    与XML中的scope属性不同,@Scope属性仅在具体的 bean 类(对于带注解的组件)或工厂方法(对于@Bean方法)上有效,不能被继承。

     

    可以实现 ScopeMetadataResolver 接口提供用于范围解析的自定义策略,而不是依赖于基于注解的方法。

    等效XML配置

     

    使用某些非单作用域时,可能有必要为作用域对象生成代理,可以使用 scoped-proxy 属性配置,下面配置产生标准的JDK动态代理。

    等效XML配置如下:

     

    4. 使用@Autowired进行自动注入

    @Autowired 注解一般用于引用类型属性的自动注入,可作用于构造器、方法、参数、成员变量和注解上。

    img

     

    应用于单个变量

    如果被注解的是单个变量,则进行唯一性匹配,即优先按类型进行匹配,如果存在多个匹配的类型,再使用变量名称进行匹配

     

    应用于数组或单列集合

    如果被注解的是数组或单列集合,则将该类型的所有 Bean 按照定义顺序依次注入到其中。

    如果你想精确控制它们的注入顺序,可以实现 Ordered接口或使用@Order/@Priority注解。

     

    应用于双列集合

    如果被注解的是一个双列集合(Key为String类型),则同样可以注入所有该类型的 Bean,并且使用 Bean 的id属性作为键值对的key值。

     

    应用于构造函数

    如果被注解的是构造函数,则表示使用该函数来实例化 Bean 对象,通过构造参数来进行属性注入。

    你可以注解多个构造函数作为“候选者”,Spring会选择参数最多的那个进行实例化,但必须保证它们的 required 属性都为false。

    如果未对构造函数使用 @Autowired 注解,则默认使用无参构造进行实例化。如果也没有无参构造,那么必须保证存在唯一的有参构造。

     

    应用于其它方法

    @Autowired 也可以支持注解 Bean 中的其它类型方法,但一般来说,传统 setter 方法使用的更多。

     

    依赖必须性

    在自动注入时,如果无法找到合适的 Bean,则抛出 NoSuchBeanDefinitionException 异常,可以通过 required 属性对此进行修改。

     

    注入内置 Bean

    使用 @Autowired 注解可以直接注入常用的可解决依赖项,而无需进行特殊设置,如BeanFactory、ApplicationContext、Environment、ResourceLoader、ApplicationEventPublisher以及MessageSource等。

     

    使用泛型作为自动装配限定符

    除了@Qualifier注解外,您还可以将 Java 泛型类型用作自动装配的匹配条件。

     

    5. 使用@Resource进行自动注入

    @Resource 是JSR-250定义的注解,与@Autowired都是用于属性注入,可以作用于类、成员变量和方法上。常用的属性有name和type。

    未指定name和type

    img

     

    同时指定name和type

    img

     

    仅指定name

    img

     

    仅指定type

    img

     

    注意事项

    数据注入注解@Autowired、@Inject、@Resource和@Value由BeanPostProcessor处理。这意味着您不能在自己的BeanPostProcessor或BeanFactoryPostProcessor类型(如果有)中应用这些注解。必须使用 XML 或 Spring @Bean方法显式“连接”这些类型。

     

     

    6. 用于注入数据的其他注解

    使用@Value注入字面量

    @Value注解一般用于字面量的注入,并且会解析${}从环境中取值进行替换。

    注意 @Value注解会优先去容器中查找名称一致的Bean进行注入,只有未找到合适的Bean时,才会进行字面量注入。

     

    使用@Required标记属性的必需性

    @Required 通常作用于 setter 方法之上,标记某个属性在实例化 Bean 时必须被注入,否则就会抛出 BeanInitializationException 异常,具体请参考 RequiredAnnotationBeanPostProcessor 的实现。

    等效的XML配置如下。

     

    使用@Primary调整自动注入优先级

    使用自动装配可能存在多个适合注入的候选对象,@Primary 注解可以指定某个 Bean 作为主 Bean,如果候选对象列表中存在唯一的主 Bean,则使用该值进行注入。

    等效的XML配置如下。

     

    使用@Qualifier进行匹配限定

    @Qualifier 用于指定一个限定符,以缩小自动装配的匹配范围,可作用于成员变量、构造方法参数或其它方法参数之上。

    假设 MovieCatalog 类型的 Bean 配置如下,则会与 SimpleMovieCatalog1 进行连接。

    同等效果的XML配置如下。

    提示:

    1. 每个Bean都会将 id 属性作为默认的限定符值,如上例也可以使用@Qualifier("simpleMovieCatalog2")来连接另一个实例。

    2. @Qualifier对集合类型变量也有效,将会在注入前限定匹配的范围(注意:Bean的限定符值并不是唯一的)。

     

    自定义限定符

    可以基于@Qualifier自定义一些组合注解,并对属性做一些修改,只有当全部属性都匹配时,才会加入候选列表。

    也可以使用CustomAutowireConfigurer来声明自定义的限定符注解,而不使用组合注解的方式。

     

     

    7. 使用JSR-330注解(了解)

    JSR-330注解是Java标准定义的依赖注入注解,Spring从3.0版本开始提供对这些注解的支持。

     

    @ManagedBean/@Named: 组件标记

    javax.annotation.ManagedBeanjavax.inject.Named可用于组件扫描,作用与@Component类似。

    并且可以使用与Spring注解完全相同的方式来配置组件扫描,如以下示例所示:

     

    @Inject/@Named: 依赖注入

    可以使用javax.inject.Inject进行属性注入,作用与@Autowired类似。

    如果要为注入的依赖项使用限定名称,还可以使用@Named注解。

    @Inject没有required属性,但可以和java.util.Optional@Nullable一起使用设置属性是否为必输。

     

    JSR-330 标准注解的局限性

    当使用标准注解时,您应该知道某些重要功能不可用,如下表所示:

    Springjavax.inject.*javax.inject 限制/注解
    @Autowired@Inject@Inject没有“必需”属性。可以与 Java 8 的Optional一起使用。
    @Component@Named/@ManagedBeanJSR-330 不提供可组合的模型,仅提供一种识别命名组件的方法。
    @Scope("singleton")@SingletonJSR-330 的默认范围类似于 Spring 的prototype。但是,为了使其与 Spring 的常规默认设置保持一致,默认情况下,在 Spring 容器中声明的 JSR-330 bean 为singleton。为了使用singleton以外的范围,您应该使用 Spring 的@Scope注解。 javax.inject还提供@Scope注解。但是,此仅用于创建自己的 注解。
    @Qualifier@ Qualifier/@ Namedjavax.inject.Qualifier只是用于构建自定义限定符的元 注解。可以通过javax.inject.Named关联具体的String限定词(如带有值的 Spring 的@Qualifier)。
    @Value-no equivalent
    @Required-no equivalent
    @Lazy-no equivalent
    ObjectFactoryProviderjavax.inject.Provider是 Spring 的ObjectFactory的直接替代方法,只是使用较短的get()方法名。它也可以与 Spring 的@Autowired或未注解的构造函数和 setter 方法结合使用。

     

     

    第六节 基于 Java 的IOC配置

    基于Java的容器配置指的是在 Java 代码中使用注解来配置 Spring 容器,其中最核心的两个注解为@Configuration@Bean。其中@Configuration 标记某个类为 Spring 的配置类组件,作为 Bean 定义的来源,而 @Bean 通过标记类中的公共方法进行 Bean 的定义。

     

    1. 使用@Bean定义Bean

    默认情况下,bean 名称为方法名称,value 为返回的对象,类型为返回值类型。

    等效的 XML 配置如下:

     

     

    2. 配置Bean的属性

    修改Bean的名称和描述

    可以使用name属性来指定bean的名称,或通过指定多个名称来设置别名。

    在必要的时候,也可以通过@Description提供更详细的文本描述。

     

    设置生命周期回调

    @Bean 注解支持指定任意的初始化和销毁回调方法,就像 XML 配置中的init-methoddestroy-method属性一样。

    你也可以在构造期间直接调用 init() 方法同样有效。

    除上之外,任何使用 @Bean 注解定义的类都支持常规的生命周期回调,并且可以使用 JSR-250 中的@PostConstruct@PreDestroy注解。同样的,如果 bean 实现了InitializingBeanDisposableBeanLifecycle,则容器将调用它们各自的方法。

    注意:默认情况下,使用Java 配置时会自动将公共的closeshutdown方法注册为销毁回调,可以使用下面方式去除。

     

    修改Bean的范围

    使用 Java 配置默认生成的 Bean 范围是 singleton,你可以使用 @Scope 注解来修改它。

    提示:有关scoped-proxy属性的使用请参考官方文档!

     

     

    3. Bean的依赖注入

    方法参数方式

    创建Bean的方法可以具有任意数量的参数,这些参数描述构建该 bean 所需的依赖关系,解析机制与基于构造函数的依赖注入几乎相同。

     

    方法调用方式

    除了使用方法参数来定义依赖外,还可以通过方法调用来定义 Bean 之间的依赖。

    等效的 XML 配置如下:

    注意:仅当在@Configuration类中声明@Bean方法时,此声明 bean 间依赖性的方法才有效。您不能通过使用普通@Component类来声明 Bean 间的依赖关系。

     

    关于方法调用定义 Bean 依赖的进一步说明

    请看如下示例,clientDao()clientService1()中被调用过一次,在clientService2()中被调用过一次,但是这两次调用返回的却是同一个实例。

    因为所有@Configuration类在启动时都使用CGLIB子类化。在子类中,子方法在调用父方法并创建新实例之前,首先检查容器中是否有任何缓存(作用域)的 bean。

     

     

    类成员注入

    Configuration类也是一个Bean,因此可以使用@Autowired@Value注入需要的依赖,在方法中使用。

    但是这种方式是不被推荐的,因为Configuration类在上下文初始化期间非常早地处理的,并且强制以这种方式注入的依赖项(如dataSource和accountRepository)可能导致意外的早期初始化。

    注意:与此类似的是,如果创建的Bean为BeanPostProcessorBeanFactoryPostProcessor,应该将方法定义为 static方法,从而防止Configuration类被过早实例化。

     

    查找方法注入

    在单例作用域的 bean 依赖于原型作用域的 bean 的情况下,通过使用 Java 配置,您可以创建CommandManager的子类,在该子类中,抽象createCommand()方法被覆盖,从而可以查找新的(原型)命令对象。

     

     

    4. 导入其它配置类

    与 XML 配置中的 import 标签一样,Java 配置中可以使用 @import 注解来导入其它配置类(或常规组件类)。

    如果需要导入的类数量比较多,还可以使用ImportSelectorImportBeanDefinitionRegistrar接口来辅助导入。

    如果想对某些配置类或Bean进行选择性导入,即在dev环境导入指定类,在test环境导入另外的类,可以使用@Profile注解,具体用法可参考:https://blog.csdn.net/ysl19910806/article/details/91646554

    注意:

    1. 使用@Import导入Bean对象时,无需对应类添加@Component组件注解。

    2. ImportSelector通常用于简单的条件导入,而ImportBeanDefinitionRegistrar用于动态注册或修改 bean 定义等更复杂的场景。

    3. ImportSelector和ImportBeanDefinitionRegistrar实现类要被@Import导入后才会生效,并且该实现类不会被注册到IOC容器中。

    4. ImportSelector的子类DeferredImportSelector也可以用于导入Bean对象,且在配置类的解析工作完成后才生效,但又比ImportBeanDefinitionRegistrar早,主要用于条件装配场景。

     

     

    5. Java和XML配置混合使用

    以XML为中心

    即使用 ClassPathXmlApplicationContext 来创建容器,注解中的配置在XML中引入,主要思路是将配置类作为一个 Bean 注册到容器中,容器会识别@Configuration注解并正确处理配置类中声明的@Bean方法。

    也可以使用注解扫描来进行配置类的Bean定义,因为@Configuration使用了@Component进行元注解。

     

    以注解为中心的配置

    即使用 AnnotationConfigApplicationContext 来创建容器,通过在@Configuration类上使用@ImportResource注解来导入外部XML配置文件。

     

     

    6. 关于在组件中定义Bean的说明

    可以在 @Component 中使用 @Bean 注解来定义其它的 Bean,但这些Bean是有限制的。

    常规 Spring 组件中的@Bean方法的处理方式与 Spring @Configuration类中相应方法的处理方式不同。不同之处在于,CGLIB 并未增强@Component类来拦截方法和字段的调用。 CGLIB 代理是调用@Configuration类中@Bean方法中的方法或字段中的字段的方法,用于创建 Bean 元数据引用以协作对象。此类方法不是用普通的 Java 语义调用的,而是通过容器进行的,以提供通常的生命周期 Management 和 Spring bean 的代理,即使通过编程调用@Bean方法引用其他 bean 时也是如此。相反,在普通@Component类内的@Bean方法中调用方法或字段具有标准 Java 语义,而无需特殊的 CGLIB 处理或其他约束。

    您可以将@Bean方法声明为static,从而允许在不将其包含的配置类创建为实例的情况下调用它们。在定义后处理器 Bean(例如,类型BeanFactoryPostProcessorBeanPostProcessor)时,这特别有意义,因为此类 Bean 在容器生命周期的早期进行了初始化,并且应避免在那时触发配置的其他部分。

    由于技术限制,对静态@Bean方法的调用永远不会被容器拦截,即使在@Configuration类中也是如此(如本节前面所述),由于技术限制:CGLIB 子类只能覆盖非静态方法。因此,直接调用另一个@Bean方法具有标准的 Java 语义,从而导致直接从工厂方法本身直接返回一个独立的实例。

    @Bean方法的 Java 语言可见性不会对 Spring 容器中的最终 bean 定义产生直接影响。您可以随意声明自己的工厂方法,以适合非@Configuration类,也可以随处声明静态方法。但是,@Configuration类中的常规@Bean方法必须是可重写的—即,不得将它们声明为privatefinal

    还可以在给定组件或配置类的 Base Class 上以及在由组件或配置类实现的接口中声明的 Java 8 默认方法上找到@Bean方法。这为组合复杂的配置安排提供了很大的灵 Active,从 Spring 4.2 开始,通过 Java 8 默认方法甚至可以实现多重继承。

    最后,单个类可以为同一个 bean 保留多个@Bean方法,这取决于在运行时可用的依赖关系,从而可以使用多个工厂方法。这与在其他配置方案中选择“最贪婪”的构造函数或工厂方法的算法相同:在构造时将选择具有最大可满足依赖关系数量的变量,类似于容器在多个@Autowired构造函数之间进行选择的方式。

    您还可以声明类型为InjectionPoint(或其更具体的子类:DependencyDescriptor)的工厂方法参数,以访问触发当前 bean 创建的请求注入点。注意,这仅适用于实际创建 bean 实例,而不适用于注入现有实例。因此,此功能对原型范围的 bean 最有意义。

     

     

     

    第七节 其它补充

    Environment接口用来表示整个应用运行时的环境,是当前Bean集合及相关属性在容器中的一个抽象,定义了下面两个重要的概念。

     

    1. Profile

    Profile用于控制哪些Bean被注册,哪些Bean不被注册,只有处于活动状态的 Bean 才会被注册。@Profile 是 Spring 为我们提供的可以根据当前环境,动态的激活和切换一系列组件的功能。

     

    为Bean配置环境

    @Profile 注解可以作用于配置类或方法之上,表示只有对应的环境被激活时,被注解的Bean才会被注册到Spring容器。

    提示

    1. 可以使用一些基本运算符来配置Bean的环境。如 production & (us-east | eu-central)

    2. 可以同时配置多个环境。如@Profile({"p1", "!p2"})表示在p1活动状态或p2未活动状态进行注册。

    3. 如果 @Profile 注解作用于同名的@Bean方法(方法重载)之上,则它们之间的配置最好相同。

    在XML配置中,可以使用 beans 标签的profile属性来配置Bean的环境,但可能会有一些限制。

     

    激活环境

    我们在启动容器时,需要指定激活的环境,否则会抛出NoSuchBeanDefinitionException。最直接的方法是通过 Environment API 以编程方式进行配置。

    当然,也可以使用声明式的方式,通过系统环境变量、JVM系统属性等方式设置 spring.profiles.active 属性的值。

    在特定环境,如WEB开发中也可以设置web.xml中的servlet 上下文参数,或测试环境中,通过 @ActiveProfiles 注解来声明。

     

    默认环境

    spring.profiles.active没有被设置时,那么Spring会根据spring.profiles.default属性的对应值来进行Profile进行激活。spring.profiles.default属性的默认值为 default,可以使用setDefaultProfiles()方法或spring.profiles.default属性来修改。

     

     

    2. @PropertySource

    Property 表示当前环境中的属性配置,属性可能来源于properties文件、JVM properties、system环境变量、JNDI、servlet context parameters上下文参数、专门的properties对象,Maps等。@PropertySource 注解用于加载指定的属性文件(properties/xml/yml)到 Spring 的 Environment 中。

     

    从环境中取出属性

    我们可以使用${}env.getProperty("")从 Environment 中取出这些值。

     

    与@Value组合使用

    @PropertySource与@Value组合使用,可以将自定义属性文件中的属性变量值注入到当前类的使用@Value注解的成员变量中。

     

    与@ConfigurationProperties组合使用(SpringBoot)

    和 @ConfigurationProperties 组合使用,可以将属性文件与一个Java类绑定,将属性文件中的变量值注入到该Java类的成员变量中。

     

     

    3. 标准和自定义事件

    ApplicationContext中的事件处理是通过ApplicationEvent类和ApplicationListener接口提供的。如果将实现ApplicationListener接口的 bean 部署到上下文中,则每次将ApplicationEvent发布到ApplicationContext时,都会通知该 bean。本质上,这是标准的 Observer 设计模式。 下表描述了 Spring 提供的标准事件:

    EventExplanation
    ContextRefreshedEvent在初始化或刷新ApplicationContext时发布(例如,通过使用ConfigurableApplicationContext接口上的refresh()方法)。在这里,“已初始化”是指所有 Bean 都已加载,检测到并激活了后处理器 Bean,已预先实例化单例,并且已准备好使用ApplicationContext对象。只要尚未关闭上下文,只要选定的ApplicationContext实际上支持这种“热”刷新,就可以多次触发刷新。例如,XmlWebApplicationContext支持热刷新,但GenericApplicationContext不支持。
    ContextStartedEventConfigurableApplicationContext界面上使用start()方法启动ApplicationContext时发布。在这里,“启动”是指所有Lifecycle bean 都收到一个明确的启动 signal。通常,此 signal 用于在显式停止后重新启动 Bean,但也可以用于启动尚未配置为自动启动的组件(例如,尚未在初始化时启动的组件)。
    ContextStoppedEventConfigurableApplicationContext接口上使用stop()方法停止ApplicationContext时发布。此处,“已停止”表示所有Lifecycle bean 都收到一个明确的停止 signal。停止的上下文可以通过start()调用重新启动。
    ContextClosedEventConfigurableApplicationContext接口上使用close()方法关闭ApplicationContext时发布。此处,“封闭”表示所有单例 bean 都被破坏。封闭的情境到了生命的尽头。无法刷新或重新启动。
    RequestHandledEvent一个特定于 Web 的事件,告诉所有 Bean HTTP 请求已得到服务。请求完成后,将发布此事件。此事件仅适用于使用 Spring 的DispatcherServlet的 Web 应用程序。

    您还可以创建和发布自己的自定义事件。以下示例显示了一个简单的类,该类扩展了 Spring 的ApplicationEventBase Class:

    要发布自定义ApplicationEvent,请在ApplicationEventPublisher上调用publishEvent()方法。通常,这是通过创建一个实现ApplicationEventPublisherAware的类并将其注册为 Spring bean 来完成的。以下示例显示了此类:

    在配置时,Spring 容器检测到EmailService实现ApplicationEventPublisherAware并自动调用setApplicationEventPublisher()。实际上,传入的参数是 Spring 容器本身。您正在通过其ApplicationEventPublisher接口与应用程序上下文进行交互。

    要接收自定义ApplicationEvent,您可以创建一个实现ApplicationListener的类并将其注册为 Spring Bean。以下示例显示了此类:

    注意,ApplicationListener通常用您的自定义事件的类型(上一示例中的BlackListEvent)进行参数化。这意味着onApplicationEvent()方法可以保持类型安全,从而避免了向下转换的任何需要。您可以根据需要注册任意数量的事件侦听器,但是请注意,默认情况下,事件侦听器会同步接收事件。这意味着publishEvent()方法将阻塞,直到所有侦听器都已完成对事件的处理为止。这种同步和单线程方法的一个优点是,当侦听器收到事件时,如果有可用的事务上下文,它将在发布者的事务上下文内部进行操作。

    以下示例显示了用于注册和配置上述每个类的 Bean 定义:

    将所有内容放在一起,当调用emailService bean 的sendEmail()方法时,如果有任何电子邮件消息应列入黑名单,则会发布BlackListEvent类型的自定义事件。 blackListNotifier bean 注册为ApplicationListener并接收BlackListEvent,此时它可以通知适当的参与者。

    Spring 的事件机制旨在在同一应用程序上下文内在 Spring bean 之间进行简单的通信。但是,对于更复杂的企业集成需求,单独维护的Spring Integration项目为构建基于众所周知的 Spring 编程模型的事件驱动的轻量级pattern-oriented体系结构提供了完整的支持。

    从 Spring 4.2 开始,您可以使用EventListener注解在托管 Bean 的任何公共方法上注册事件侦听器。

    方法签名再次声明其侦听的事件类型,但是这次使用灵活的名称并且没有实现特定的侦听器接口。只要实际事件类型在其实现层次结构中解析您的通用参数,也可以通过通用类型来缩小事件类型。

    如果您的方法应该侦听多个事件,或者您要完全不使用任何参数来定义它,则事件类型也可以在注解本身上指定。以下示例显示了如何执行此操作:

    也可以通过使用定义SpEL expression的注解的condition属性来添加其他运行时过滤,该属性应匹配以针对特定事件实际调用该方法。

    以下示例显示了仅当事件的content属性等于my-event时,才可以重写我们的通知程序以进行调用:

    每个SpEL表达式都针对专用上下文进行评估。下表列出了可用于上下文的项目,以便您可以将它们用于条件事件处理:

    NameLocationDescriptionExample
    Eventroot object实际的ApplicationEvent#root.event
    Arguments arrayroot object用于调用目标的参数(作为数组)。#root.args[0]
    Argument nameevaluation context任何方法参数的名称。如果由于某种原因名称不可用(例如,因为没有调试信息),则参数名称也可以在#a<#arg>下获得,其中#arg代表参数索引(从 0 开始)。#blEvent#a0(您也可以使用#p0#p<#arg>表示法作为别名)

    请注意,即使您的方法签名实际上引用了已发布的任意对象,#root.event也使您可以访问基础事件。

    如果由于处理另一个事件而需要发布一个事件,则可以更改方法签名以返回应发布的事件,如以下示例所示:

    asynchronous listeners不支持此功能

    此新方法为上述方法处理的每个BlackListEvent发布一个新的ListUpdateEvent。如果您需要发布多个事件,则可以返回Collection事件。

     

    如果希望特定的侦听器异步处理事件,则可以重用常规@Async 支持。以下示例显示了如何执行此操作:

    使用异步事件时,请注意以下限制:

    1. 如果事件监听器抛出Exception,则它不会传播到调用者。有关更多详细信息,请参见AsyncUncaughtExceptionHandler。

    2. 此类事件侦听器无法发送答复。如果您需要发送另一个事件作为处理结果,请注入ApplicationEventPublisher以手动发送事件。

     

    如果需要先调用一个侦听器,则可以将@Order注解添加到方法声明中,如以下示例所示:

     

    您还可以使用泛型来进一步定义事件的结构。考虑使用EntityCreatedEvent<T>,其中T是已创建的实际实体的类型。例如,您可以创建以下侦听器定义以仅接收泛型类型为Person的EntityCreatedEvent:

    由于类型擦除,只有在触发的事件解析了事件侦听器所依据的通用参数(即诸如class PersonCreatedEvent extends EntityCreatedEvent<Person> { … })时,此方法才起作用。

    在某些情况下,如果所有事件都遵循相同的结构,这可能会变得很乏味(就像前面示例中的事件一样)。在这种情况下,您可以实现ResolvableTypeProvider以指导框架超出运行时环境提供的范围。以下事件显示了如何执行此操作:

     

     

    4. 加载时编织(LoadTimeWeaver)

    如果需要在将类加载到 Java 虚拟机(JVM)中时对其进行动态转换,则可使用下面方式开启加载时编织。

    等效的XML配置如下:

    一旦为ApplicationContext配置,该ApplicationContext内的任何 bean 都可以实现LoadTimeWeaverAware,从而接收到对加载时编织器实例的引用。

     

     

    5. 使用 MessageSource 进行国际化

     

    第02章_AOP切面

    AOP全称为Aspect Oriented Programming,中文译为面向切面编程。它是一种通过预编译动态代理的方式在不修改源代码的情况下给程序动态添加某种特定功能的技术,用来弥补面向对象编程(OOP)中的一些不足,它更关注于多个类之间的共同问题,如日志记录,性能统计,安全控制,事务处理,异常处理等。

    下面表格列举了AOP中的一些常用概念。

    名词解释
    Joinpoint(连接点)可供被拦截的方法或字段
    Pointcut(切入点)想要拦截的连接点
    Advice(通知/增强)拦截之后要做的事
    Introduction(引介)一种特殊的通知,可以为现有对象添加任何接口的实现
    Aspect(切面)是切入点和通知(引介)的结合
    Advisors(顾问)一种特殊的切面,只包含一个通知(引介),是SpringAOP中独有的概念
    Weaving(织入)把切面的功能应用到业务类的过程

    Spring框架以IOC容器为基础,引入了AspectJ相关注解和类库,实现了基于方法拦截的AOP支持,用于提供声明式企业服务,并且使用户也能够基于IOC容器做一些自定义的切面功能。

    SpringAOP是纯Java实现的,采用运行期动态代理织入,不需要特殊的编译过程或控制类加载器的层次结构。如果你仅需要拦截Bean的一些方法,执行额外的逻辑,推荐你使用SpringAOP,因为这更加简单,不需要在开发和构建过程中引入 AspectJ 编译器/编织器。但如果你想拦截非Spring管理对象(如域对象)中的方法,或者想拦截字段,那么请参考AspectJ的使用。

     

     

    第一节 定义切面

    切面是切入点和通知(引介)的结合,你可以通过AspectJ语言的相关注解声明和配置一个切面。

     

    1. 导入相关Jar包

    首先导入SpringAOP开发过程中需要使用的jar包spring-aop.jaraspectjweaver.jar等。

     

    2. 使用注解配置切面

    Spring基于AspectJ相关注解提供AOP注解配置,默认情况下,Spring 不会检测AspectJ相关注解,需要通过Java配置或XML配置开启。

     

    如果你是基于Java配置的项目,可以在配置类上加@EnableAspectJAutoProxy注解开启注解AOP支持。

    如果你是基于XML配置的项目,可以在Spring配置文件中加入<aop:aspectj-autoproxy/>元素开启注解AOP支持。

     

    注解配置开关开启后,即可通过org.aspectj.lang.annotation.Aspect注解将Bean声明为一个切面,Spring在创建相关Bean时,会检测Bean上的AspectJ注解,并使用动态代理进行AOP配置。

    注意:关于声明切面的几点注意事项

    1. @Aspect注解不足以在 Classpath 中被自动检测,因此需要再添加一个@Component注解。

    2. 切面本身不能成为其他切面的目标,标记某个类为切面的同时,会自动从其它切面的动态代理列表排除。

     

    3. 使用XML配置切面

    使用XML方式配置AOP切面,首先需导入相关的Schema约束(参见附录中的[AOP Schema][https://www.docs4dev.com/docs/zh/spring-framework/5.1.3.RELEASE/reference/core.html#xsd-schemas-aop]),引入aop名称空间。然后,使用<aop:config>标签按顺序配置切入点切面通知等元素。

    注意<aop:config>样式的配置大量使用了 Spring 的auto-proxying机制。如果您已经通过使用BeanNameAutoProxyCreator或类似方法来使用显式自动代理,则可能会导致问题(例如未编制建议)。

     

     

    第二节 声明切入点

    切入点指想要拦截的方法或字段,通过切入点表达式来确定哪些连接点作为切入点。需要注意的是,Spring中仅支持方法类型的切入点。

     

    1. 使用注解声明切入点

    可以直接在通知上配置切入点表达式来声明切入点。

     

    也可以在void方法上加@Pointcut注解来定义单独的切入点,然后在通知上进行引用(注意加小括号)。

     

    2. 使用XML声明切入点

    可以直接在通知配置的pointcut属性配置切入点表达式。

     

    但一般来说,会单独定义切入点配置元素,提供给多个通知引用。

     

    3. 使用接口声明切入点

    本节介绍如何通过实现接口的方式进行切入点的配置,虽然Spring5仍支持这种方式,但一般不推荐使用。

     

    切入点接口org.springframework.aop.Pointcut定义如下,实现该接口,即可定义一个切入点,用于定义想要被拦截的方法。

    切入点接口又分为ClassFilterMethodMatcher两个子接口,分别用于类匹配和方法匹配,定义如下。

    静态切入点和动态切入点

    • 静态切入点是基于方法和目标类的,不能考虑方法的参数,从而只在首次调用方法时评估一次静态切入点并进行缓存。最常见的是使用元数据属性进行匹配的元数据驱动切入点。

    • 动态切入点在静态切入点的基础上考虑了方法参数值,这意味着在每次调用方法时都需要根据方法参数评估切入点,有一定性能损耗。常见的动态切入点有控制流切入点org.springframework.aop.support.ControlFlowPointcut

     

    从Spring2.0开始,你可以使用org.springframework.aop.aspectj.AspectJExpressionPointcut接口来定义一个AspectJ表达式切入点,切入点表达式的具体用法,请参考前面章节。

     

     

    正则表达式切入点是一种常用的静态切入点,基于JDK正则表达式的实现类为JdkRegexpMethodPointcut,示例如下。

    Spring 提供了一个名为RegexpMethodPointcutAdvisor的顾问类封装了切入点和通知,简化了使用。

     

     

    第三节 制作通知

    通知指拦截后要执行的逻辑,一般与切入点表达式关联使用。Spring支持的通知类型有:前置通知(Before Advice)、后置通知(AfterReturning Advice)、异常通知(AfterThrowing Advice)、最终通知(After Advice)和环绕通知(Around Advice)。

     

    1. 前置通知

    前置通知在切入点方法执行之前执行,可以使用@Before注解标识切面中的某个方法为前置通知。

     

     

    也可以通过实现org.springframework.aop.MethodBeforeAdvice接口定义一个前置通知。前置通知允许在连接点方法调用前执行一些自定义逻辑,但不能更改返回值,如果前置通知出现异常,则会传播回拦截器链。

     

    2. 后置通知(正常返回)

    后置通知在切入点方法执行之后执行,可以使用@AfterReturning注解标识切面中的某个方法为后置通知。如果想在后置通知中获取切入点方法返回的实际值,可以使用注解的returning属性指定一个参数名称接收返回值,并且限制仅匹配参数类型与业务方法返回值类型兼容的方法。

     

    doAccessCheck方法必须声明一个名为retVal的参数,该参数的类型以与@AfterReturning相同的方式约束匹配。

     

    通过实现org.springframework.aop.AfterReturningAdvice接口定义一个后置通知。后置通知可以访问返回值(不能被修改),方法的参数和目标对象等运行时信息。

     

     

    3. 异常通知

    异常通知在切入点方法抛出异常时执行(与catch代码块执行时机类似),可以使用@AfterThrowing注解标识切面中的某个方法为异常通知。通常,我们只想对部分特殊的异常进行拦截,并且希望能够在通知中获取异常信息,这可以使用throwing属性来指定一个参数名称接收抛出的异常。

     

    doRecoveryActions方法必须声明一个名为dataAccessEx的参数,该参数的类型以与@AfterThrowing相同的方式约束匹配。

     

    通过实现org.springframework.aop.ThrowsAdvice接口定义一个异常通知。由于为了兼容类型化参数,因此ThrowsAdvice接口中无任何抽象方法。

    你可以定义一个或四个参数的afterThrowing方法来适应不同的处理场景,并支持定义在一个类中,如下示例。

    如果异常通知本身引发异常,则它将覆盖原始异常,通常重写为 RuntimeException 类型,与任何方法签名都兼容。如果需要重写为受检异常,则必须与目标方法的已声明异常相匹配。

     

     

     

    4. 最终通知

    最终通知在方法执行完毕退出时执行(与finally代码块执行时机类似),可以使用@After注解标识切面中的某个方法为最终通知。

     

     

     

    5. 环绕通知

    环绕通知在方法执行的四个阶段都可以添加额外逻辑,相当于前四种通知的集合,可以使用@Around注解标识切面中的某个方法为环绕通知。通常用于需要以线程安全的方式在方法执行之前和之后共享状态的情形下使用,例如启动和停止计时器。如果前四种通知能够满足你的需求,那么尽量不要使用环绕通知。

    环绕通知的第一个参数必须为ProceedingJoinPoint类型,通过调用其Object proceed()Object proceed(Object[] var1)方法,即可执行原业务逻辑,该方法可以在通知中被执行一次,多次甚至0次!

    环绕通知的返回值类型应该与业务方法的返回值类型一致或是其子类,否则将会出现类型转换异常。

     

    与注解配置类似,环绕通知的第一个参数也必须是ProceedingJoinPoint类型。

     

    通过实现org.aopalliance.intercept.MethodInterceptor接口定义一个环绕通知,并通过MethodInvocation来获取运行时相关信息和进行连接点方法的调用。

     

     

    第四节 切入点详解

    1. 切入点指示符

    切入点表达式有多种类型,通过不同的切入点指示符(PCD)来区分,Spring支持以下几种AspectJ定义的切入点指示符。

    除此之外,Spring基于自身特性,扩展了一种附加指示符bean,其格式为bean(idOrNameOfBean),并且也支持*通配符和切入点表达式的组合运算。

    扩展:完整AspectJ语言支持的其它切入点指示符

    完整的 AspectJ 切入点语言还支持 call,get,set,preinitialization,staticinitialization,initialization,handler,adviceexecution,withincode,cflow,cflowbelow,if,@this和@withincode指示符,但你不能在SpringAOP中使用它们,否则会抛出IllegalArgumentException。

     

    2. 切入点表达式

    SpringAOP用户最常用的切入点指示符为execution,其切入点表达式的格式如下:

    模式是否必须说明
    modifiers-pattern(修饰符模式) 匹配指定的修饰符类型
    ret-type-pattern(返回类型模式)匹配指定的返回值类型
    declaring-type-pattern(声明类型模式) 匹配指定的包名和类名
    name-pattern(名称模式)匹配指定的方法名称
    param-pattern(参数模式)匹配指定的参数个数和类型
    throws-pattern(异常类型模式) 匹配指定的异常类型

    下面是一些切入点表达式的示例。

    使用切入点表达式的一些建议

    • this、target和args通常以绑定形式使用,@target、@within、@annotation和@args也可以以绑定形式使用。

    • AspectJ会在编译期间对切入点进行DNF(析取范式)重写处理,并且对切入点的组件进行排序,以优化匹配性能。

    • 指示符可分为kinded(execution/get/set/call/handler), scoping(within/withincode)和contextual(this/target/@annotation)三类,好的切入点表达式应至少包含前两类,否则由于额外的分析和处理,很有可能会影响编织性能。

     

    3. 表达式的组合运算

    你可以使用&&, ||!对切入点表达式进行组合,最佳实践是从较小的命名组件中构建更复杂的切入点表达式,如下例所示。

    接口方式同样支持切入点的组合运算,实现org.springframework.aop.support.ComposablePointcut接口定义一个组合切入点,使用unionintersection等方法即可和其它切入点、类或方法进行组合运算。

     

    4. 共享切入点表达式

    对于一些常用的切入点表达式,可以单独定义一个切面来进行存放,只需将@Pointcut注解所在的方法声明为public方法即可。

    在XML中可以将切入点表达式定义在切面配置之外,提供给多个切面使用。

    还支持在XML方式的切入点表达式配置中直接引用注解方式配置好的切入点。

     

     

    第五节 通知详解

    1. 访问连接点

    任何类型的通知都可以将第一个参数声明为org.aspectj.lang.JoinPoint类型,用于获取当前连接点对象,从而获取更多信息。

     

    2. 绑定参数

    如果需要在通知中使用方法实参,可使用切入点标识符args通过参数名称进行绑定,在调用通知时会将相应参数的值作为参数值传递。

    此外,还可以通过切入点的方式传递参数。当切入点Account对象值与连接点匹配时,该切入点“提供”,然后从通知中引用命名切入点。

    代理对象(this),目标对象(target)和注解(@within,@target,@annotation和@args)都可以以类似的方式绑定。如下示例展示了如何匹配带@Auditable注解的方法,并获取注解中的AuditCode值。

     

    此外,基于XML方式的AOP配置也支持收集连接点上下文。例如,以下切入点收集this对象作为连接点上下文,并将其传递给通知。

    注意,在声明通知时,需要通过包含匹配名称的参数来接收收集的连接点上下文,如下所示。

    说明:

    1. XML 文档中的&&很尴尬,因此可以分别使用and,or和not关键字代替&&,||和!。

    2. 请注意,以这种方式定义的切入点由其 XML id引用,并且不能用作命名切入点以形成复合切入点。

     

    下面是一个参数使用示例。有一个业务代码,需要对其进行拦截。

    接下来是切面和通知的代码逻辑。

    然后对切面进行配置及一些必要的其它配置。

     

     

    3. 通知参数和泛型

    Spring AOP可以处理类声明和方法参数中使用的泛型。假设您具有如下通用类型:

    您可以通过在要拦截方法的参数类型中键入通知参数,将方法类型的拦截限制为某些参数类型:

    但这种方法不适用于通用集合。因此,您不能按以下方式定义切入点:

    为了使这项工作有效,我们将不得不检查集合中的每个元素,这是不合理的,因为我们也无法决定通常如何处理null值。要实现类似目的,您必须将参数键入Collection<?>并手动检查元素的类型。

     

    4. 确定参数名称

    通知调用中的参数绑定依赖于切入点表达式中使用的名称与通知及切入点方法签名中声明的参数名称的匹配。在某些情况下,无法通过Java反射获得参数名称,因此 Spring AOP 使用以下策略来确定参数名称:

     

    基于XML的AOP配置与注解一样支持完全类型化的通知,即通过名称和通知方法参数匹配切入点参数。当然,你也可以通过arg-names 属性显示指定通知方法的参数名称列表,以逗号分隔。

     

     

    5. 如何处理通知参数

    前面我们提到过,我们将描述如何使用在 Spring AOP 和 AspectJ 上始终有效的参数编写proceed调用。解决方案是确保建议签名按 Sequences 绑定每个方法参数。以下示例显示了如何执行此操作:

    在许多情况下,无论如何都要进行此绑定(如上例所示)。

     

    6. 通知的顺序

    当某一切入点存在多个相同类型的通知时,SpringAOP遵循与AspectJ相同的优先级规则来确定通知执行的顺序。即前置通知先执行优先级高的,后置通知、异常通知和最终通知先执行优先级低的。优先级可以通过实现org.springframework.core.Ordered接口或使用Order注解来修改。特殊情形,如果这些通知存在于同一个切面时,无法指定其之间优先级,只能考虑将这些通知合并为一个通知,或者重构为单独的切面类。

    在Spring5.0及之后,相同优先级时,环绕通知会在最外层执行,以包含其他类型通知。

     

    7. 定义新的通知类型

    org.springframework.aop.framework.adapter软件包是 SPI 软件包,可在不更改核心框架的情况下添加对新的自定义通知类型的支持。自定义Advice类型的唯一限制是它必须实现org.aopalliance.aop.Advice标记接口。

     

     

    第六节 AOP代理创建

    1. AOP代理方式

    如果要代理的目标对象实现至少一个接口,则使用 JDK 动态代理,代理了由目标类型实现的所有接口。如果目标对象未实现任何接口,则将创建 CGLIB 代理。

    要强制使用 CGLIB 代理,请将<aop:config>元素的proxy-target-class属性的值设置为true,如下所示:

    注意,多个<aop:config/>节在运行时会折叠到一个统一的自动代理创建器中,该创建器将应用<aop:config/>节中的任何(通常来自不同 XML bean 定义文件)指定的代理设置。这也适用于<tx:annotation-driven/><aop:aspectj-autoproxy/>元素。也就是说,在<tx:annotation-driven/><aop:aspectj-autoproxy/><aop:config/>元素上使用proxy-target-class="true"会强制对所有三个元素使用 CGLIB 代理。

    注意两种代理方式的优略:

    1. JDK代理必须实现接口,且只对public方法有效。

    2. CGLib代理要求类和方法不能被final修饰,是可变类。

     

    2. 配置方式创建

    FactoryBean是Spring提供配置复杂Bean的方式之一,可以通过其实现类 ProxyFactoryBean 来配置所需的代理对象。

     

    ProxyFactoryBean从org.springframework.aop.framework.ProxyConfig继承了许多有用的属性,默认值都为false

    属性名属性说明
    proxyTargetClass是否使用CGLib创建代理(CGLib将会代理目标类而非目标类的接口)
    optimize是否对CGLib创建的代理应用积极的优化策略
    frozen是否禁止在创建代理后通过Advised接口更改配置
    exposeProxy是否在ThreadLocal中公开当前代理,公开后目标可以使用AopContext.currentProxy()获取

    此外,ProxyFactoryBean本身也带有一些可配置的属性。

    属性名属性说明
    proxyInterfaces将代理的接口列表(String[]),如未提供,则使用CGLib代理
    interceptorNames要应用的通知、Advisor或拦截器(String[]),拦截顺序取决于列表中的顺序
    singleton是否为单例,默认为true

     

    下面是一个使用ProxyFactoryBean配置AOP代理的示例,其中目标对象实现了一个业务接口Person,默认将会采用JDK代理方式。

    在需要目标对象personTarget的地方,完全可以使用代理对象person来替代,并会执行额外拦截逻辑。

    如果需要切换到CGLib代理,则可以配置 proxyTargetClass 属性为true,但必须注意,目标类不能被final修饰。

     

    也可以将目标对象定义为匿名内部Bean进行隐藏,防止应用程序获取到未被代理的对象。

     

     

     

    首先,为代理对象创建父模板,定义如下。由于配置了abstract属性为true,该Bean定义将不会被实例化,相关知识可参考SpringIOC章节介绍的Bean继承。

    后续在创建代理对象时可通过parent指定父模板来复用一些属性。

    当然,对于不合适的属性也可以选择覆盖。

     

    3. 编程方式创建

    可以在代码中通过代理工厂来创建所需的AOP代理。

     

    使用ProxyFactory可以直接创建目标对象的AOP代理,而不需要依赖IOC容器,如下示例:

    如需使代理对象实现其它接口,可以添加IntroductionInterceptionAroundAdvisor类型的顾问。

     

    除此之外,还可以通过AspectJProxyFactory创建可添加切面的AOP代理。

    ProxyFactory、AspectJProxyFactory和之前提到的ProxyFactoryBean都是ProxyCreatorSupport的直接子类

     

    4. 自动扫描创建

    上面介绍的ProxyFactoryBean、ProxyFactory和AspectJProxyFactory等都是手工创建AOP代理的方式,我们还可以通过IOC容器的Bean后处理器在加载时修改Bean定义来自动创建AOP代理。

    BeanNameAutoProxyCreator是内置的BeanPostProcessor之一,注册后会自动为名称与beanNames属性相匹配的Bean创建AOP代理,支持*通配符。

    DefaultAdvisorAutoProxyCreator 是一种更通用、功能更强大的自动代理创建者,注册后会自动检测上下文中的顾问,并根据顾问的切入点和通知(拦截器)自动创建AOP代理,后续从上下文获取bean对象时,会被替换为代理对象。

    注意

    1. DefaultAdvisorAutoProxyCreator只对上下文中的顾问有效,并不会自动配置定义的通知或拦截器。

    2. 类似的后处理器还有 AspectJAwareAdvisorAutoProxyCreator和AnnotationAwareAspectJAutoProxyCreator等。

     

     

    5. 修改代理对象的配置

    所有用于创建AOP代理对象的类都继承了ProxyCreatorSupport,而其实现了Advised接口,因此所有的AOP代理都可以被强制转换为Advised类型。该接口包括以下方法:

    注意:默认情况下,即使已创建代理,也可以添加或删除顾问。但引介顾问除外,因为工厂的现有代理不会显示接口更改,你必须重新从工厂获取新的代理对象。

     

    以下示例显示了将AOP代理强转到Advised接口并检查和处理其顾问。

     

    6. AOP代理存在问题

    有下面一个普通的Java类以及通常的使用方式。

    再来看下使用AOP代理后的使用方式。

    这里pojo是一个代理对象,调用其foo方法时,代理可以委派给与之匹配的通知。但一旦调用链到达未被代理的实际业务对象(这里指SimplePojo的实例),执行foo方法,在其内部调用bar时,this指的是未被代理的实际业务对象,此时,将没有机会被委派到与之匹配的通知,从而使AOP代理失效。

    为了避免这种情况,最好的办法是进行代码重构,以免发生自调用。但Spring也提供了一种解决方案,但这将会导致你的代码完全耦合到 Spring AOP。

    最后,修改下我们的main方法,在创建代理时setExposeProxy(ture),来暴露代理对象在上下文中,这样就可以使foo()方法的自身调用bar()方法的AOP代理正常工作了。

    注意:AspectJ 没有此自调用问题,因为它不是基于代理的 AOP 框架。

     

     

    第七节 其它补充

    1. 引介

    引介(Introduction)是一种特殊的通知,可以为现有对象添加任何接口的实现。通过使用@DeclareParents注解定义一个引介。

    等效的XML配置及Java代码如下。

    此外,在前面示例的通知中,服务 Bean 可以直接用作UsageTracked接口的实现来使用。

    提示:

    你也可以使用IntroductionInterceptorIntroductionAdvisor来定义引介,具体介绍请参考Spring官方文档。

     

    2. 顾问

    顾问(Advisors)可以看作是一个独立的小切面,只有一条通知,通知本身由 Bean 表示,并且必须实现Spring通知类型中描述的通知接口之一。这是SpringAOP中独有的概念,在AspectJ中没有直接等效的配置。

    顾问通过<aop:advisor>标签配置,最常见的使用场景为Spring的声明式事务配置。

    提示:

    1. 同切面类似的,顾问同样支持使用 pointcut 属性来定义内联切入点表达式,及使用order属性配置优先级

    2. 你也可以通过继承org.springframework.aop.support.DefaultPointcutAdvisor类的方式定义Advisor。

     

     

    3. 目标源

    目标源org.springframework.aop.TargetSource用于在AOP代理处理方法调用时获取目标对象。如果未指定目标源,则使用默认实现包装本地对象,每次调用都会返回相同的目标。

    使用目标源时,目标对象通常是原型Bean,这样,Spring才可以在需要的时候创建一个新对象。

     

    使用可热交换目标源org.springframework.aop.target.HotSwappableTargetSource创建的AOP代理允许对目标对象进行替换。

    您可以使用 HotSwappableTargetSource 上的swap()方法更改目标,如以下示例所示。该操作是线程安全的,并且会立即生效。

     

    池目标源维护了业务Bean的实例池,基于Commons Pool 2.2提供了默认实现CommonsPool2TargetSource,导入commons-pool.jar包后即可使用。

    注意:

    1. Spring还支持Commons Pool 1.5,但从 Spring Framework 4.2 开始不推荐使用。

    2. 你可以继承org.springframework.aop.target.AbstractPoolingTargetSource支持其它池化技术。

     

    你可以配置下面顾问,允许通过引介技术将任何池化对象强转为org.springframework.aop.target.PoolingConfig类型,以获取池的大小等相关配置信息。

    通过在AbstractPoolingTargetSource类上调用便捷方法(因此使用MethodInvokingFactoryBean)可获得此顾问程序。该顾问的名称(此处为poolConfigAdvisor)必须位于公开池对象的ProxyFactoryBean中的拦截器名称列表中。如下获取池的最大大小。

     

    原型目标源PrototypeTargetSource与池目标源类似,每次方法调用都会创建目标的新实例。

     

    如果需要为每个线程创建新的目标对象,则可以使用ThreadLocal目标源。

     

     

    4. 切面实例化模型

    默认情况下,应用程序上下文中每个切面都有一个实例。 AspectJ 将此称为单例实例化模型。可以使用备用生命周期来定义切面。 Spring 支持 AspectJ 的perthispertarget实例化模型(当前不支持percflow, percflowbelow,和pertypewithin)。

    您可以通过在@Aspect注解中指定perthis子句来声明perthis切面。

    perthis子句的作用是为每个执行业务服务的唯一服务对象(每个与切入点表达式匹配的联接点绑定到“ this”的唯一对象)创建一个切面实例。切面实例是在服务对象上首次调用方法时创建的。当服务对象超出范围时,切面将超出范围。在创建切面实例之前,其中的任何建议都不会执行。创建切面实例后,在其中声明的通知将在匹配的连接点处执行,但仅当服务对象是与此切面相关联的对象时才执行。有关per子句的更多信息,请参见 AspectJ 编程指南。

    pertarget实例化模型的工作方式与perthis完全相同,但是它为匹配的连接点处的每个唯一目标对象创建一个方面实例。

    注意:使用XML配置方式目前仅支持单例实例化模型,在将来的版本中可能会支持其他实例化模型。

     

     

    5. Spring与Aspect

    请参考Spring官方文档 Core 篇的 5.10 章节Using AspectJ with Spring Applications

    注意:

    1. Spring AOP 默认通过运行期的动态代理来实现AOP的,可额外导入 spring-aspects 依赖来支持通过类加载时编织实现AOP。

     

    6. AOP配置示例

    对于一些可重复执行的操作,在执行失败时自动进行重试,该功能涉及多个类的逻辑修改,非常适合使用切面实施。

    相应的 Spring 配置如下。请注意,该切面实现了Ordered接口,因此我们可以将切面的优先级设置为高于事务通知,这样就可以在每次重试的时候开启新事务了。

    为了优化切面使用,仅对指定的方法进行重试,我们可以定义一个@Idempotent注解,对需要重试的方法进行标记。

    然后,修改切面的切入点表达式,仅对有@Idempotent注解的方法进行匹配,如下所示:

    如果你更喜欢通过XML进行AOP配置,可参考如下配置示例,配套的Java类去掉AOP相关注解即可。

     

    更多案例请打开SpringAOP-demo工程查看!

    • demo01:基于XML的事务切面

    • demo02:基于注解的事务切面

     

     

    7. SpringAOP相关工具类

    Spring读源码系列之AOP--05---aop常用工具类学习大忽悠爱忽悠的博客-CSDN博客aop 工具类](https://blog.csdn.net/m0_53157173/article/details/124361587)

    第03章_事务控制

    第一节 Spring事务管理简介

    Spring为不同类型的事务管理提供了统一的访问接口,使用起来更加简便。

     

    1. Spring事务相关接口

    PlatformTransactionManager

    Spring事务管理的核心接口为org.springframework.transaction.PlatformTransactionManager,其声明了事务操作的基本API。

     

    TransactionDefinition

    事务的相关特性由org.springframework.transaction.TransactionDefinition接口定义。

    注意

    1. 隔离级别和超时等参数只有在开启新事务时有效,如果是返回当前活动事务,则不会生效。

    2. 如果设置了不支持的事务配置选项,则会事务管理器会抛出异常(read-only标志除外)。

    关于事务传播行为

    • 如果当前不存在活动事务时:

      1. 创建新事务: 0-PROPAGATION_REQUIRED3-PROPAGATION_REQUIRES_NEW、6-PROPAGATION_NESTED

      2. 非事务执行:1-PROPAGATION_SUPPORTS、4-PROPAGATION_NOT_SUPPORTED、5-PROPAGATION_NEVER

      3. 抛异常:2-PROPAGATION_MANDATORY

    • 如果当前存在活动事务时:

      1. 加入:0-PROPAGATION_REQUIRED、1-PROPAGATION_SUPPORTS、2-PROPAGATION_MANDATORY,要么都提交,要么都回滚(cc-11,rr-00,cr-00,rc-00E,即任一事务回滚,则整个事务都会滚)。

      2. 挂起:3-PROPAGATION_REQUIRES_NEW(cc-11,rr-00,cr-10,rc-01)、4-PROPAGATION_NOT_SUPPORTED(cc-11,rr-10,cr-10,rc-11),互不干涉。

      3. 抛异常:5-PROPAGATION_NEVER,直接抛异常。

      4. 创建保存点:6-PROPAGATION_NESTED,可以基于保存点回滚(cc-11,rr-00,cr-00,rc-01)。

     

    TransactionStatus

    事务的状态和行为由org.springframework.transaction.TransactionStatus接口定义。

     

    2. PlatformTransactionManager的常用实现

    DataSourceTransactionManager

    不管是使用声明式事务,还是编程式事务,都必须先注册PlatformTransactionManager的具体实现。如果你使用纯JDBC(或MyBatis),则需要注册DataSourceTransactionManager。DataSourceTransactionManager类是单个JDBC数据源的PlatformTransactionManager实现,它将JDBC连接从指定的数据源绑定到当前正在执行的线程,可能允许每个数据源一个线程连接。

     

    HibernateTransactionManager

    如果你使用的是 Hibernate 本地事务,则需要注册HibernateTransactionManager

    注意:如果DataSource(由任何非JTA事务管理器使用)通过 JNDI 查找并由 Java EE 容器管理,则它应该是非事务性的,因为 Spring 框架(而不是 Java EE 容器)Management 事务。

     

    JtaTransactionManager

    如果是在JavaEE容器环境中,通过 JNDI 查找数据源,则一般注册JtaTransactionManager。JtaTransactionManager不需要了解DataSource(或任何其他特定资源),因为它使用了JavaEE容器的全局事务管理基础结构。

    提示:如果您使用JTA,则无论使用哪种数据访问技术(无论是JDBC、HibernateJPA或任何其他受支持的技术),事务管理器定义都应该看起来相同。这是由于 JTA 事务是全局事务,它可以征用任何事务资源。

    从上面几个案例可以看到,在所有这些情况下,无需更改应用程序代码。您可以仅通过更改配置来更改事务的管理方式,即使更改意味着从本地事务转移到全局事务,反之亦然。

     

     

    3. 事务管理器与资源同步

    上面介绍了如何创建不同的事务管理器,并连接事务所需要的资源(如DataSourceTransactionManager所需的JdbcDataSource等),本节将会介绍在业务代码中如何正确创建、重用和清理这些资源。

    首选的方法是使用Spring提供的 JdbcTemplate 等一些高级抽象的解决方案,在其内部处理了资源的创建、重用和清理,以及事务同步和异常处理,使你可以完全专注于业务代码实现。

    当然,如果你希望业务代码可以直接访问底层资源,可以通过DataSourceUtils(JDBC)、EntityManagerFactoryUtils(JPA)、SessionFactoryUtils(Hibernate)等类来获取被Spring管理的底层资源实例。

     

    第二节 声明式事务管理

    Spring声明式事务管理基于SpringAOP实现,可以做到方法级别的精细控制,并且可以自定义回滚规则,设置Rollback-Only状态,以及在事务管理的过程中插入自定义行为等。下面是声明式事务处理的基本流程。

    tx

    注意:Spring事务不支持跨远程调用传播事务上下文,即事务不能跨远程调用。

     

    1. 声明式事务的基本配置

    下面实例,为x.y.service包下的类创建AOP代理,并应用事务通知。

    下面是一个测试代码。

     

    2. 配置多个事务通知

    你可以为不同的Bean配置不同的事务通知。

     

    3. 事务通知配置详解

    下表是事务通知的一些属性和配置含义。

    属性名描述默认值
    name方法名称。可用*表示任意字符。 
    propagation事务传播行为。详情参考上一章节内容。REQUIRED
    isolation事务隔离级别。仅适用于REQUIREDREQUIRES_NEW的传播设置。DEFAULT
    timeout事务超时(秒)。仅适用于传播REQUIREDREQUIRES_NEW-1
    read-only读写与只读事务。仅适用于REQUIREDREQUIRES_NEWfalse
    rollback-for触发回滚的异常列表,以逗号分隔。 
    no-rollback-for不触发回滚的异常列表,以逗号分隔。 

    下面是事务回滚异常列表配置示例。默认情况下,如果出现运行时异常(RuntimeException)错误(Error),则回滚事务,否则提交事务。你可以通过设置回滚规则(rollback-for)或不回滚规则(no-rollback-for)来修改需要回滚事务的异常。

    你也可以使用编程式事务控制来实现上述控制,但这会使你的代码与Spring框架耦合,尽量不要使用它。

     

     

    4. 声明式事务的注解配置方式

    除了使用XML进行声明式事务配置外,还可以使用Spring提供的注解方式进行配置,主要用到的注解为@EnableTransactionManagement@Transactional

     

    开启事务注解配置支持

    首先,在配置类上加@EnableTransactionManagement注解,开启注解事务支持。

     

    下面表格是@EnableTransactionManagement注解的一些属性配置。

    注解属性XML属性描述默认值
    不适用transaction-manager要使用的事务管理器bean名称transactionManager
    modemode默认的proxy-表示使用SpringAOP代理。aspectj-表示使用AspectJ编织,这需要spring-aspects.jar包,并启用加载时编织或编译时编织。proxy
    proxyTargetClassproxy-target-class是否使用CGLib创建基于子类的动态代理,仅适用于proxy模式。false
    orderorder事务通知的应用顺序,一般为最低优先级(值越大优先级越低)。Ordered.LOWEST_PRECEDENCE

     

    等效的XML配置如下:

     

    在类或方法上开启事务

    在类或方法上加@Transactional注解,Spring在创建相关Bean时,会自动进行AOP代理,应用事务通知。

     

    下面表格是@Transactional 注解的一些属性配置。

    属性名描述默认值
    value要使用的事务管理器bean名称或限定符值transactionManager
    propagation事务传播行为PROPAGATION_REQUIRED
    isolation事务隔离级别ISOLATION_DEFAULT
    timeout事务超时时间。-1表示基础事务系统的默认超时时间或无超时-1
    readOnly事务是否只读false
    rollbackFor/rollbackForClassName触发回滚的异常列表(Class/String类型),以逗号分隔。 
    noRollbackFor/noRollbackForClassName不触发回滚的异常列表(Class/String类型),以逗号分隔。 

    注意:显示在事务监视器(例如,WebLogic 的事务监视器)和日志输出中的事务名称暂时无法指定,但它一般是全类名.方法名,例如,BusinessService类的handlePayment(..)方法启动了事务,则事务的名称应为com.example.BusinessService.handlePayment。

     

    下面是使用@Transactional注解的几点注意事项:

    1. @Transactional注解只会对public方法生效,protected和private方法上的该注解将会被忽略。

    2. 不建议将@Transactional注解加在接口上,因为它只适用于基于接口的AOP代理,而在创建基于子类的AOP代理时无效。

    3. 也不建议将@Transactional注解加在父类上,因为子类不会继承该注解。

    4. 只有通过代理对象调用public方法才会进行事务控制,通过this(实际对象)的自调用无法执行事务通知,不进行事务控制。

    5. 你不能在未完全初始化时依赖Spring提供的事务控制,比如在@PostConstruct标注的方法中,这时可能尚未应用事务通知。

     

    选择不同的事务管理器

    一般情况下,Spring应用程序使用一个事务管理器就够了,但某些特殊情形,你可能需要多个独立的事务管理器。这时,可以通过@Transactional注解的value属性来指定所需要的事务管理器bean名称或指定一个匹配限定符。

    如下案例演示了如何使用限定符来选择不同的事务管理器。首先,先配置两个不同的事务管理器。

    然后,在代码中通过限定符来进行匹配。

    提示:如果限定符匹配不到事务管理器,则会选用默认的事务管理器transactionManager。

     

    利用组合注解简化事务配置

    如果发现您在许多不同的方法上重复使用@Transactional的相同属性,则可将其定义为自定义的组合注解,简化配置。

    自定义注解定义好后,就可以在代码中使用了。

     

     

     

    第三节 编程式事务管理

    Spring提供了两种编程式事务管理的方式,分别是使用TransactionTemplate和直接执行 PlatformTransactionManager。

    1. TransactionTemplate

    TransactionTemplate与JdbcTemplate等模板的设计类似,采用回调的方式传入业务代码。

     

    执行事务方法

    如果执行的方法带有返回值,执行时传入TransactionCallback接口的实例。

    如果执行的方法不带返回值,执行时传入TransactionCallbackWithoutResult接口的实例。

    如果需要进行回滚,可以调用TransactionStatus对象上的setRollbackOnly()方法来回滚事务,如下所示:

    transactionTemplate.execute(new TransactionCallbackWithoutResult() {

    });

     

    修改事务配置

    可以调用TransactionTemplate的相关方法修改事务配置,例如传播模式,隔离级别,超时等。

    定义共享的事务模板实例

    以下示例通过使用 Spring XML 配置来定义具有一些自定义事务设置的TransactionTemplate。

    然后,您可以根据需要将sharedTransactionTemplate注入到尽可能多的服务中。

    注意:

    1. TransactionTemplate类的实例是线程安全的,因为该实例不维护任何对话状态。

    2. 但TransactionTemplate实例会维持配置状态,因此尽管许多类可以共享一个TransactionTemplate的单个实例,但是如果一个类需要使用具有不同设置(例如,不同的隔离级别)的TransactionTemplate,则需要创建两个不同的TransactionTemplate实例。

     

    2. PlatformTransactionManager

    您也可以直接使用org.springframework.transaction.PlatformTransactionManager来管理您的事务。以下示例显示了如何执行此操作:

     

    第四节 其它

    1. 事务绑定事件

    如果你希望发布的事件在事件源事务的特定阶段触发(一般是事务提交后触发),则可以使用@TransactionalEventListener来替代原有的@EventListener注解。如下示例:

    特别的,可以使用其 phase 属性指定触发时机,可选的值有BEFORE_COMMIT,AFTER_COMMIT(默认),AFTER_ROLLBACK和AFTER_COMPLETION。

    如果不存在事务,默认不调用侦听器,但你也可以设置 fallbackExecution 属性设置为true来覆盖该行为。

     

    2. 在 AspectJ 中使用@Transactional

     

     

    3. 与特定服务器的集成

     

     

    第04章_ORM框架

    Spring框架对JDBC进行了封装和抽象,隐藏了许多样板化的使用细节,提供了多种更加方便的操作方式,如JdbcTemplate和NamedParameterJdbcTemplateSimpleJdbcInsert和SimpleJdbcCall以及MappingSqlQuery、SqlUpdate和StoredProcedure等。除此之外,还将JDBC的SqlException异常(甚至Hibernate或JPA等框架特有的异常)转换为自己的异常层次结构(DataAccessException),使您可以轻松地在上述持久性技术之间进行切换。

    下面列表展示了使用JDBC查询数据库时Spring为你做了哪些事项:

    ActionWho?
    1. 定义连接参数
    2. 打开连接
    3. 指定 SQL 语句
    4. 声明参数并提供参数值
    5. 准备并执行该语句
    6. 设置循环以遍历结果(如果有)
    7. 进行每次迭代的工作
    8. 处理任何异常
    9. 处理事务
    10. 关闭连接,语句和结果集

    提示:确保您的数据访问对象(DAO)或存储库进行异常转换的最佳方法是使用@Repository注解。

     

     

    第一节 JdbcTemplate

    JdbcTemplate是Spring提供的操作数据库最基本的方式。它负责处理资源的创建和释放(如打开和关闭连接)以及JDBC工作流程的基本任务(如PS的创建和执行)等,只需应用程序提供SQL和对执行结果进行处理即可。

     

    1. 配置Jdbctemplate

    1.1 XML方式配置

    常见的做法是配置所需的数据源为Bean,并将其注入到Dao类中,然后在数据源的set方法中创建JdbcTemplate。

    与之配套的XML配置如下:

    除此之外,也可直接在XML中配置一个默认的JdbcTemplate,直接注入JdbcTemplate到Dao类中。

    简单起见,在实际项目过程中,可以使Dao类继承JdbcDaoSupport类,则会继承来自其的setDataSource(..)方法和setJdbcTemplate(...)方法,使用Dao类时直接注入其中任意一个即可。

    注意:JdbcTemplate与Spring的其它模板类相似,其会话数据是线程安全的,但是配置信息(如数据库连接信息)是线程不安全的。

     

    1.2 注解方式配置

    Spring也支持使用注解方式配置JdbcTemplate,一般采用@Repository注解将Dao配置为Bean,然后使用@Autowired注解注入数据源。

    与之配套的XML配置如下:

     

    2. 使用JdbcTemplate

    2.1 查询单行记录

    关于RowMapper:

    它提供一个回调函数mapRow(ResultSet rs, int rowNum),用于告诉JdbcTemplate如何从结果集拿数据并封装到VO对象。特别的,如果列名和字段名一致,可以使用new BeanPropertyRowMapper<>(User.class)来简化,下面将会使用到该种方式。

     

     

    2.2 查询多行记录

     

     

    2.3 查询返回结果集

     

    2.4 执行更新语句

    提示:在JDBC中,insert、update和delete语句都视为更新语句,处理逻辑一致!

     

    注意:

    1. 插入时检索自增列值需要驱动支持JDBC3.0标准。

    2. 需要自定义PreparedStatement,在第二个字段指定自增列名。

     

     

     

    2.5 执行DDL语句

     

    2.6 批量更新

    注意必须保证Object数组中对象的顺序和设置参数时的顺序一致!

     

    注意:如果流或从文件中读取,可能最后一批可能没有该数量的条目。在这种情况下,您可以使用InterruptibleBatchPreparedStatementSetter接口,在合适的时候调用isBatchExhausted方法中断批处理。

     

    2.7 分批次批量更新

    该方法返回值为一个二维数组,外层数组长度表示已执行的批次数,内层数组的长度表示每批处理的数量,元素的值表示一次更新语句的返回值。如果JDBC程序不支持,则为-2。

     

    3. NamedParameterJdbcTemplate

    NamedParameterJdbcTemplate舍弃传统占位符(?)的方式,使用更语义化的变量名称来占位。其参数通过MapSqlParameterSource的形式传入。SqlParameterSource有三个实现类,分别为MapSqlParameterSourceBeanPropertySqlParameterSourceEmptySqlParameterSource,下面将会介绍它们的使用。

     

    3.1 Map<String,Object>类型参数

     

    3.2 MapSqlParameterSource类型参数

     

    3.3 BeanPropertySqlParameterSource类型参数

    提示:

    1. 除了上述两个实现类,SqlParameterSource还有一个空实现EmptySqlParameterSource,常用来占位使用。

    2. NamedParameterJdbcTemplate是对JdbcTemplate的封装,如果需要访问包装的JdbcTemplate实例以访问仅在JdbcTemplate类中提供的功能,可以使用getJdbcOperations()方法获取

     

    3.4 批量更新

     

    提示SqlParameterSourceUtils.createBatch可你帮助你将List<BizObject>转换为SqlParameterSource[]类型!

     

    4. SQLExceptionTranslator

    SQLExceptionTranslator用于将数据库异常转换为Spring中定义的DataAccessException异常。根据不同的转换策略,定义了三个实现类,分别是SQLErrorCodeSQLExceptionTranslatorSQLExceptionSubclassTranslatorSQLStateSQLExceptionTranslator

     

    4.1 异常转换流程

    默认情况下,使用SQLErrorCodeSQLExceptionTranslator进行数据库异常的转换,它通过SQLErrorCodesFactory加载类路径下的名为sql-error-codes.xml的文件,并通过数据库元数据中的DatabaseProductName查找匹配的SQLErrorCodes用于异常转换。该文件位于org.springframework.jdbc.support包下,配置示例如下。

    提示:该文件可以被类路径根目录下同名文件覆盖!

    如果使用SQLErrorCodeSQLExceptionTranslator不能转换数据库异常,则采用后备转换器SQLExceptionSubclassTranslator,如果仍不能转换,再使用SQLStateSQLExceptionTranslator。

     

    4.2 扩展异常转换器

    你可以对SQLErrorCodeSQLExceptionTranslator进行扩展,如下示例,将特定的错误代码(-12345)转换为DeadlockLoserDataAccessException异常,而其他错误则仍由默认转换器实现转换。

    若要使此转换器生效,必须通过setExceptionTranslator方法将其传递给JdbcTemplate。

    注意:在创建CustomSQLErrorCodesTranslator时需要传递一个数据源,以获取数据库元数据查找sql-error-codes.xml对应配置信息。

     

    第二节 其它补充

    1. 高级抽象的数据访问方式

    1.1 SimpleJdbc

    SimpleJdbcInsert和SimpleJdbcCall通过JDBC驱动程序提供数据库元数据来简化用户配置。下面是一些简单示例,具体介绍可参考文档

     

    1.2 JdbcObject

    org.springframework.jdbc.object包包含一些类,这些类使您可以以更加面向对象的方式访问数据库,具体介绍请参考官方文档。

     

     

    2. 特殊参数和值的处理

    2.1 提供参数的JDBC类型

    一般来说,Spring可以根据传入参数的Java类型来推断JDBC类型。但如果参数值为null时,可能无法识别Java类型,则需要调用ParameterMetaData.getParameterType来获取JDBC类型,这对JDBC驱动程序来说可能很昂贵。你可以升级最新的驱动程序版本,并将spring.jdbc.getParameterType.ignore属性设置为true来禁用此功能,并且通过下面一些方式对可能为null的参数显式传入JDBC类型。

     

    2.2 传入IN子句的值列表

    对如类似select * from T_ACTOR where id in (1, 2, 3)的SQL语句,由于JDBC驱动不支持可变数量的占位符,因此对于IN子句的参数列表设置需要动态拼接字符串来实现。在Spring中,可以使用NamedParameterJdbcTemplate来让Spring帮你完成该拼接过程,你只需要传入一个参数值列表即可。

    特别的,如果你的数据库支持类似select * from T_ACTOR where (id, last_name) in ((1, 'Johnson'), (2, 'Harrop'))的语法,你可以传入一个对象列表,Spring会自动进行处理。

    注意:一般数据库对IN中的条目数有限制,列如Oracle限制为1000。

     

    2.3 处理BLOB和CLOB对象

    JdbcTemplate支持BLOB(二进制大对象)和CLOB(字符型大对象)类型的参数值和查询结果处理。详情查看官方文档。

     

    2.4 对存储过程和函数的支持

    可以通过SimpleJdbcCall和StoredProcedure来操作存储过程或函数,还支持使用特定于数据库的复杂类型。详情查看官方文档。

     

    3. 嵌入式数据库支持

    嵌入式数据库由于其轻量级的特性,易于配置并且启动时间短,在开发和测试的过程中非常有用。Spring在org.springframework.jdbc.datasource.embedded包提供对嵌入式数据库引擎的支持,内置了HSQLH2Derby三种嵌入式数据库。此外,你也可以使用扩展API来支持新的嵌入式数据库类型。

     

    3.1 创建嵌入式数据库

    提示

    1. 默认的数据库类型为EmbeddedDatabaseType.HSQL,你可以通过type属性修改为H2DERBY

    2. 请将generate-name属性设置为true,生成唯一名称,否则在创建嵌入式数据库时会被连接到同名的数据库实例上。

     

     

     

    3.2 使用嵌入式数据库

    嵌入式数据库的使用和其它JDBC数据源的使用方式类似。

    提示:如果你在多个测试类中使用,应该将它注册到Spring容器中管理。

     

     

    3.3 扩展嵌入式数据库

    您可以通过两种方式扩展 Spring JDBC 嵌入式数据库的支持:

     

     

    4. 关于数据源

    数据源(DataSource)是JDBC规范的一部分,是通用的连接工厂,Spring通过数据源获得与数据库的连接。你可以从JNDI查找数据源,也可以使用第三方提供的连接池实现来配置自己的数据源。

     

    4.1 创建数据源
    DriverManagerDataSource

    等效的代码如下:

    注意

    1. DriverManagerDataSource是一个Spring内置的数据源,每次获取时都返回新连接,不对连接进行缓存,仅用于测试目的。与此类似的还有其实现类SingleConnectionDataSource

    2. 更多关于Spring对javax.sql.DataSource的实现可以查看SmartDataSource接口和AbstractDataSource抽象类。

     

    DBCP DataSource

     

    C3P0 DataSource

     

    Druid DataSource

     

    Hikari DataSource

    提示:如果想使创建的数据源参与Spring管理的事务,可以使用TransactionAwareDataSourceProxy进行包装,但这并不推荐,操作数据库时最好是使用JdbcTemplate等更加抽象的API。

    jdbc.properties配置

    下面是jdbc.properties文件的常用配置:

     

    4.2 初始化数据源

    在容器启动时,如果需要对数据源进行初始化,可以使用org.springframework.jdbc.datasource.init包提供的数据源初始化功能。

    注意

    1. 如果您需要更精细的控制,可以直接使用DataSourceInitializer并将其定义为应用程序中的组件。

    2. 如果其它Bean在Spring容器启动过程中依赖数据源的初始化,则必须注意它们的先后顺序。

     

    4.3 获取数据库连接

    在Spring应用中,数据库连接一般通过DataSourceUtils来获取,这样可以参与到Spring管理的事务之中。DataSourceUtils类是一种方便且功能强大的工具类,它提供静态方法从数据源获取连接并在必要时关闭连接,常用的方法如下。

    注意

    1. Jdbctemplate等一些Spring提供的类获取连接也是通过DataSourceUtils来进行的。

    2. 代码中最好不要使用jdbcTemplate.getDataSource().getConnection()的方式来获取连接,这个连接不是事务线程上下文绑定的连接,不参与Spring管理的事务,并且还必须手动调用close()方法去关闭它。

    3. 如果处于事务上下文中,那么开发者不需要显示关闭或者释放连接,但是如果 DataSourceUtils 在没有事务上下文的方法中使用 getConnection() 获取连接,依然会造成数据连接泄漏,这个时候就需要显示release了。

    第05章_快速测试

    第一节 SpringTest简介

    1. SpringTest是什么?

    SpringTest可以方便的对Spring程序进行单元测试和集成测试,一般与Junit或TestNG等测试工具整合使用。

     

    2. SpringTest整合Junit4

    1) 引入相关依赖

     

    2) 编写业务类

     

    3) 编写测试类

     

     

    3. SpringTest整合Junit5

    1) 引入相关依赖

     

    2) 编写业务类

     

    3) 编写测试类

     

     

    第二节 相关注解介绍

    1. @ContextConfiguration

    @ContextConfiguration用于指定ApplicationContext的配置信息,可以来自XML、Groovy、@Configuration类或Spring的其它组件等。

    这些配置可以通过类继承的方式来继承和覆盖:

     

    2. @ActiveProfiles

    @ActiveProfiles用于指定测试时激活的Profile。

     

    3. @TestPropertySource

    @TestPropertySource用于配置测试属性文件和优先级更高的内联测试属性,并将其添加到Environment中的PropertySources集合中,以便为集成测试加载ApplicationContext。

    提示

    1. @TestPropertySource配置的属性优先级高于OS环境变量、Java系统属性、@PropertySource属性以及编程方式添加的属性源加载的属性。

    2. @TestPropertySource也支持通过类继承的方式进行继承和覆盖。

     

    4. 事务相关注解

     

     

    5. @Sql/@SqlConfig/@SqlGroup

    这三个注解用于在测试方法之前或之后执行一些SQL脚本,默认执行的脚本为类目录下的.sql文件,如com.example.MyTest类的默认脚本为classpath:com/example/MyTest.sql。

     

    6. 其它不常用注解

     

     

    第三节 其它补充知识

    1. 常用测试工具

    SpringTest包中提供了一些测试可能用到的工具类,如ReflectionTestUtils、AopTestUtils和JdbcTestUtils等,可参考相关官方文档。