实习------Spring 框架学习

news2024/9/28 19:25:39

Spring 是什么(了解)

在不同的语境中,Spring 所代表的含义是不同的。下面我们就分别从“广义”和“狭义”两个角度,对 Spring 进行介绍。

广义上的 Spring 泛指以 Spring Framework 为核心的 Spring 技术栈。
经过十多年的发展,Spring 已经不再是一个单纯的应用框架,而是逐渐发展成为一个由多个不同子项目(模块)组成的成熟技术,例如 Spring Framework、Spring MVC、SpringBoot、Spring Cloud、Spring Data、Spring Security 等,其中 Spring Framework 是其他子项目的基础。

狭义的 Spring 特指 Spring Framework,通常我们将它称为 Spring 框架。
Spring 框架是一个分层的、面向切面的 Java 应用程序的一站式轻量级解决方案,它是 Spring 技术栈的核心和基础,是为了解决企业级应用开发的复杂性而创建的。
Spring 有两个核心部分: IoC 和 AOP。

Spring的优缺点是什么?

优点

(1)解耦合

Spring将所有对象的创建和依赖关系的维护交给IOC容器管理实现了解耦合

(2)AOP编程的支持

Spring提供面向切面编程,把业务逻辑和系统服务分开,可以方便的实现对程序进行权限拦截、日志管理等功能。

(3)声明式事务的支持

只需要通过配置就可以完成对事务的管理,而无需手动编程。方便程序的测试

(5)Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架的直接支持

(如:Struts、Hibernate、MyBatis等)

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

(7)Spring 提供方便的API把具体技术相关的异常(比如由JDBC,Hibernate or JDO抛

出的)转化为一致的unchecked 异常。

缺点

(一)框架组件复杂
我们看到 Spring 架构图时会发现 Spring 里面包含有很多其他组件,比如数据访问、MVC、事务管理、面向切点、WebSocket 功能等,因此这么复杂的组件集中到一起就会提高初学者的学习成本。

(二)集成复杂
比如我们想要使用 MyBatis 或者 MongoDB的时候,我们要做很多工作

(三)配置复杂
在使用 Spring 的时候,我们更多可能是选择 XML 进行配置

(四)构建和部署复杂
启动 Spring 的 IOC 容器,是完全要依赖于第三方的 web 服务器。自身不能启动的。

Spring 体系结构(熟悉)

上图中包含了 Spring 框架的所有模块,这些模块可以满足一切企业级应用开发的需求,在开发过程中可以根据需求有选择性地使用所需要的模块。下面分别对这些模块的作用进行简单介绍。

1. Data Access/Integration(数据访问/集成)

数据访问/集成层包括 JDBC、ORM、OXM、JMS 和 Transactions 模块,具体介绍如下。

  • JDBC 模块:提供了一个 JBDC 的样例模板,使用这些模板能消除传统冗长的 JDBC 编码还有必须的事务控制,而且能享受到 Spring 管理事务的好处。
  • ORM 模块:提供与流行的“对象-关系”映射框架无缝集成的 API,包括 JPA、JDO、Hibernate 和 MyBatis 等。而且还可以使用 Spring 事务管理,无需额外控制事务。
  • OXM 模块:提供了一个支持 Object /XML 映射的抽象层实现,如 JAXB、Castor、XMLBeans、JiBX 和 XStream。将 Java 对象映射成 XML 数据或者将XML 数据映射成 Java 对象。
  • JMS 模块:指 Java 消息服务,提供一套 “消息生产者、消息消费者”模板用于更加简单的使用 JMS,JMS 用于用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。
  • Transactions 事务模块:支持编程和声明式事务管理。

2. Web 模块

Spring 的 Web 层包括 Web、Servlet、WebSocket 和 Portlet 组件,具体介绍如下。

  • Web 模块:提供了基本的 Web 开发集成特性,例如多文件上传功能、使用的 Servlet 监听器的 IOC 容器初始化以及 Web 应用上下文。
  • Servlet 模块:提供了一个 Spring MVC Web 框架实现。Spring MVC 框架提供了基于注解的请求资源注入、更简单的数据绑定、数据验证等及一套非常易用的 JSP 标签,完全无缝与 Spring 其他技术协作。
  • WebSocket 模块:提供了简单的接口,用户只要实现响应的接口就可以快速的搭建 WebSocket Server,从而实现双向通讯。
  • Portlet 模块:提供了在 Portlet 环境中使用 MVC 实现,类似 Web-Servlet 模块的功能。

3. Core Container(Spring 的核心容器)

Spring 的核心容器是其他模块建立的基础,由 Beans 模块、Core 核心模块、Context 上下文模块和 SpEL 表达式语言模块组成,没有这些核心容器,也不可能有 AOP、Web 等上层的功能。具体介绍如下。

  • Beans 模块:提供了框架的基础部分,包括控制反转和依赖注入。
  • Core 核心模块:封装了 Spring 框架的底层部分,包括资源访问、类型转换及一些常用工具类。
  • Context 上下文模块:建立在 Core 和 Beans 模块的基础之上,集成 Beans 模块功能并添加资源绑定、数据验证、国际化、Java EE 支持、容器生命周期、事件传播等。ApplicationContext 接口是上下文模块的焦点。
  • SpEL 模块:提供了强大的表达式语言支持,支持访问和修改属性值,方法调用,支持访问及修改数组、容器和索引器,命名变量,支持算数和逻辑运算,支持从 Spring 容器获取 Bean,它也支持列表投影、选择和一般的列表聚合等。

4. AOP、Aspects、Instrumentation 和 Messaging

在 Core Container 之上是 AOP、Aspects 等模块,具体介绍如下:

  • AOP 模块:提供了面向切面编程实现,提供比如日志记录、权限控制、性能统计等通用功能和业务逻辑分离的技术,并且能动态的把这些功能添加到需要的代码中,这样各司其职,降低业务逻辑和通用功能的耦合。
  • Aspects 模块:提供与 AspectJ 的集成,是一个功能强大且成熟的面向切面编程(AOP)框架。
  • Instrumentation 模块:提供了类工具的支持和类加载器的实现,可以在特定的应用服务器中使用。
  • messaging 模块:Spring 4.0 以后新增了消息(Spring-messaging)模块,该模块提供了对消息传递体系结构和协议的支持。

5. Test 模块

Test 模块:Spring 支持 Junit 和 TestNG 测试框架,而且还额外提供了一些基于 Spring 的测试功能,比如在测试 Web 框架时,模拟 Http 请求的功能。

Spring IoC 容器 (熟悉)

IoC

IoC(Inverse of Control:控制反转)是一种设计思想,就是将原本在程序中手动创建对象的控制权和对象之间的相互依赖关系交由Spring的ioc容器来管理, 当某个对象需要其他协作对象时,由Spring动态的通过依赖注入(DI, Dependency Injection)的方式来提供协作对象。IoC 容器是 Spring 用来实现 IoC 思想的载体, IoC 容器实际上就是个Map(key,value),Map 中存放的是各种对象

在传统的 Java 应用中,一个类想要调用另一个类中的属性或方法,通常会先在其代码中通过 new Object() 的方式将后者的对象创建出来,然后才能实现属性或方法的调用。为了方便理解和描述,我们可以将前者称为“调用者”,将后者称为“被调用者”。也就是说,调用者掌握着被调用者对象创建的控制权。
但在 Spring 应用中,Java 对象创建的控制权是掌握在 IoC 容器手里的,其大致步骤如下。

  1. 开发人员通过 XML 配置文件、注解、Java 配置类等方式,对 Java 对象进行定义,例如在 XML 配置文件中使用 <bean> 标签、在 Java 类上使用 @Component 注解等。
  2. Spring 启动时,IoC 容器会自动根据对象定义,将这些对象创建并管理起来。这些被 IoC 容器创建并管理的对象被称为 Spring Bean。
  3. 当我们想要使用某个 Bean 时,可以直接从 IoC 容器中获取(例如通过 ApplicationContext 的 getBean() 方法),而不需要手动通过代码(例如 new Obejct() 的方式)创建。

IoC 带来的最大改变不是代码层面的,而是从思想层面上发生了“主从换位”的改变。原本调用者是主动的一方,它想要使用什么资源就会主动出击,自己创建;但在 Spring 应用中,IoC 容器掌握着主动权,调用者则变成了被动的一方,被动的等待 IoC 容器创建它所需要的对象(Bean)。
这个过程在职责层面发生了控制权的反转,把原本调用者通过代码实现的对象的创建,反转给 IoC 容器来帮忙实现,因此我们将这个过程称为 Spring 的“控制反转”。

DI

DI(Dependency Injection),即“依赖注入”,在系统运行中,IoC容器动态的向某个对象提供它所依赖的其他对象,也就是根据依赖关系把某个对象所依赖的其他对象给自动注入到这个对象中,这也就是为什么叫依赖注入,DI的实现的通过反射来实现的。

DI其他描述

依赖注入(Dependency Injection),即组件之间的依赖关系由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中

理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:

  • 谁依赖于谁:当然是应用程序依赖于IoC容器;
  • 为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;
  • 谁注入谁:很明显是IoC容器注入应用程序某个对象所依赖的对象;
  • 注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。

IoC和DI由什么关系呢?

其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊,只能看出容器控制对象这一个层面,很难让人看出谁来维护对象关系,所以后面引用依赖注入,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置的依赖对象”。

总的来说,其实是息息相关的关系,Ioc的实现离不开Di,Di的实现又要依赖于ioc。

Ioc的实现离不开Di是指的Ioc在管理对象之间的依赖关系的时候需要通过Di来实现依赖注入,把一个对象所依赖的其他对象注入到这个对象中来

Di的实现又要依赖于ioc是指di能够把一个对象所依赖的其他对象注入到这个对象中来是因为对象的控制权已经转交给了ioc容器,所以di的实现是依赖于ioc的

举个栗子

比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。

另外一种描述

(1)IOC就是控制反转,是指创建对象的控制权的转移。以前创建对象的主动权和时机是由自己把控的,而现在这种权力转移到Spring容器中,并由容器根据配置文件去创建实例和管理各个实例之间的依赖关系。对象与对象之间松散耦合,也利于功能的复用。DI依赖注入和控制反转是同一个概念的不同角度的描述,即 应用程序在运行时依赖IoC容器来动态注入对象需要的外部资源。

(2)最直观的表达就是,IOC让对象的创建不用去new了,可以由spring自动生产,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的。

(3)Spring的IOC有三种注入方式 :构造器注入、setter方法注入、根据注解注入。

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

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

最大的作用就是解耦合,将原本在程序中手动创建对象的控制权和对象之间的相互依赖关系交由Spring的ioc容器来管理,把对象的管理交给交给容器来管理,需要用到对象只需要从容器里拿,降低代码的耦合度

SpringIOC 实现机制

在 Java 软件开发过程中,系统中的各个对象之间、各个模块之间、软件系统和硬件系统之间,或多或少都存在一定的耦合关系。

若一个系统的耦合度过高,那么就会造成难以维护的问题,但完全没有耦合的代码几乎无法完成任何工作,这是由于几乎所有的功能都需要代码之间相互协作、相互依赖才能完成。因此我们在程序设计时,所秉承的思想一般都是在不影响系统功能的前提下,最大限度的降低耦合度。

IoC 底层通过工厂模式、Java 的反射机制、XML 解析等技术,将代码的耦合度降低到最低限度,其主要步骤如下。

  1. 在配置文件(例如 Bean.xml)中,对各个对象以及它们之间的依赖关系进行配置;
  2. 我们可以把 IoC 容器当做一个工厂,这个工厂的产品就是 Spring Bean;
  3. 容器启动时会加载并解析这些配置文件,得到对象的基本信息以及它们之间的依赖关系;
  4. IoC 利用 Java 的反射机制,根据类名生成相应的对象(即 Spring Bean),并根据依赖关系将这个对象注入到依赖它的对象中。

由于对象的基本信息、对象之间的依赖关系都是在配置文件中定义的,并没有在代码中紧密耦合,因此即使对象发生改变,我们也只需要在配置文件中进行修改即可,而无须对 Java 代码进行修改,这就是 Spring IoC 实现解耦的原理。

Spring 如何设计容器的?

Spring 作者 Rod Johnson 设计了BeanFactory 和ApplicationContext这两个接口用以表示容器。

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

ApplicationContext 可以称之为 “高级容器”。除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能,例如支持不同信息源头,支持BeanFactory工具类,支持层级容器,支持访问文件资源,支持事件发布通知,支持接口回调等等。

BeanFactory和ApplicationContext的关系

BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。ApplicationContext接口作为BeanFactory的子类,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能,例如支持不同信息源头,支持BeanFactory工具类,支持层级容器,支持访问文件资源,支持事件发布通知,支持接口回调等等。

ApplicationContext通常的实现是什么

1、FileSystemXmlApplicationContext
ApplicationContext act = new FileSystemXmlApplicationContext("D:/javaWorkSpace/spring2/src/applicationContext.xml");
person persons = (person) act.getBean("persons");
persons.show();
2、ClassPathXmlApplicationContext
ApplicationContext act = new ClassPathXmlApplicationContext("applicationContext.xml");
Hello hello = (Hello) act.getBean("hello");
hello.show();
3、AnnotationContigApplicationContext
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
Hello hello = (Hello) ctx.getBean("hello");

区别

Spring Bean 定义 (熟悉)

由 Spring IoC 容器管理的对象称为Spring Bean,Spring Bean 根据 Spring 配置文件中的信息创建。

我们可以把 Spring IoC 容器看作是一个大工厂,Bean 相当于工厂的产品。如果希望这个大工厂生产和管理 Bean,就需要告诉容器需要哪些 Bean,以哪种方式装配。

Spring 配置文件支持两种格式,即 XML 文件格式和 Properties 文件格式。

  • Properties 配置文件主要以 key-value 键值对的形式存在,只能赋值,不能进行其他操作,适用于简单的属性配置。
  • XML 配置文件采用树形结构,结构清晰,相较于 Properties 文件更加灵活。但是 XML 配置比较繁琐,适用于大型的复杂的项目。

通常情况下,Spring 的配置文件都是使用 XML 格式的。XML 配置文件的根元素是 <beans>,该元素包含了多个子元素 <bean>。每一个 <bean> 元素都定义了一个 Bean,并描述了该 Bean 是如何被装配到 Spring 容器中的。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <bean id="helloWorld" class="net.biancheng.c.HelloWorld">
        <property name="message" value="Hello World!"/>
    </bean>
</beans>

在 XML 配置的<beans> 元素中可以包含多个属性或子元素,常用的属性或子元素如下表所示。 

属性名称描述
idBean 的唯一标识符,Spring IoC 容器对 Bean 的配置和管理都通过该属性完成。id 的值必须以字母开始,可以使用字母、数字、下划线等符号。
name该属性表示 Bean 的名称,我们可以通过 name 属性为同一个 Bean 同时指定多个名称,每个名称之间用逗号或分号隔开。Spring 容器可以通过 name 属性配置和管理容器中的 Bean。
class该属性指定了 Bean 的具体实现类,它必须是一个完整的类名,即类的全限定名。
scope表示 Bean 的作用域,属性值可以为 singleton(单例)、prototype(原型)、request、session 和 global Session。默认值是 singleton。
constructor-arg<bean> 元素的子元素,我们可以通过该元素,将构造参数传入,以实现 Bean 的实例化。该元素的 index 属性指定构造参数的序号(从 0 开始),type 属性指定构造参数的类型。
property<bean>元素的子元素,用于调用 Bean 实例中的 setter 方法对属性进行赋值,从而完成属性的注入。该元素的 name 属性用于指定 Bean 实例中相应的属性名。
ref<property> 和 <constructor-arg> 等元素的子元索,用于指定对某个 Bean 实例的引用,即 <bean> 元素中的 id 或 name 属性。
value<property> 和 <constractor-arg> 等元素的子元素,用于直接指定一个常量值。
list用于封装 List 或数组类型的属性注入。
set用于封装 Set 类型的属性注入。
map用于封装 Map 类型的属性注入。
entry <map> 元素的子元素,用于设置一个键值对。其 key 属性指定字符串类型的键值,ref 或 value 子元素指定其值。
init-method容器加载 Bean 时调用该方法,类似于 Servlet 中的 init() 方法
destroy-method容器删除 Bean 时调用该方法,类似于 Servlet 中的 destroy() 方法。该方法只在 scope=singleton 时有效
lazy-init懒加载,值为 true,容器在首次请求时才会创建 Bean 实例;值为 false,容器在启动时创建 Bean 实例。该方法只在 scope=singleton 时有效

Spring Bean 作用域(熟悉)

默认情况下,所有的 Spring Bean 都是单例的,也就是说在整个 Spring 应用中, Bean 的实例只有一个,这样可以更好地重用对象,节省重复创建对象的开销。

singleton 是 Spring 容器默认的作用域。当 Bean 的作用域为 singleton 时,Spring IoC 容器中只会存在一个共享的 Bean 实例。这个 Bean 实例将存储在高速缓存中,所有对于这个 Bean 的请求和引用,只要 id 与这个 Bean 定义相匹配,都会返回这个缓存中的对象实例。

在 Spring 配置文件中,可以使用 <bean> 元素的 scope 属性,将 Bean 的作用域定义成singleton,其配置方式如下所示:

<bean id="..." class="..." scope="singleton"/>

如果一个 Bean 定义的作用域为 prototype,那么这个 Bean 就被称为 prototype bean。对于 prototype bean 来说,Spring 容器会在每次请求该 Bean 时,都创建一个新的 Bean 实例。

从某种意义上说,Spring  IoC 容器对于 prototype bean 的作用就相当于 Java 的 new 操作符。它只负责 Bean 的创建,至于后续的生命周期管理则都是由客户端代码完成的

在 Spring 配置文件中,可以使用 <bean> 元素的 scope 属性将 Bean 的作用域定义成 prototype,其配置方式如下所示:

<bean id="..." class="..." scope="prototype"/>

我们可以在 <bean> 元素中添加 scope 属性来配置 Spring Bean 的作用范围。例如,如果每次获取 Bean 时,都需要一个新的 Bean 实例,那么应该将 Bean 的 scope 属性定义为 prototype,如果 Spring 需要每次都返回一个相同的 Bean 实例,则应将 Bean 的 scope 属性定义为 singleton。

Spring 5 共提供了 6 种 scope 作用域,如下表。

作用范围描述
singleton默认值,单例模式,表示在 Spring 容器中只有一个 Bean 实例
prototype原型模式,表示每次通过 Spring 容器获取 Bean 时,容器都会创建一个新的 Bean 实例。
request每次 HTTP 请求,容器都会创建一个 Bean 实例。该作用域只在当前 HTTP Request 内有效。
session同一个 HTTP Session 共享一个 Bean 实例,不同的 Session 使用不同的 Bean 实例。该作用域仅在当前 HTTP Session 内有效。
application同一个 Web 应用共享一个 Bean 实例,该作用域在当前 ServletContext 内有效。

与 singleton 类似,但 singleton 表示每个 IoC 容器中仅有一个 Bean 实例,而一个 Web 应用中可能会存在多个 IoC 容器,但一个 Web 应用只会有一个 ServletContext,也可以说 application 才是 Web 应用中货真价实的单例模式。
websocketwebsocket 的作用域是 WebSocket ,即在整个 WebSocket 中有效。

注意:在以上 6 种 Bean 作用域中,除了 singleton 和 prototype 可以直接在常规的 Spring IoC 容器(例如 ClassPathXmlApplicationContext)中使用外,剩下的都只能在基于 Web 的 ApplicationContext 实现(例如 XmlWebApplicationContext)中才能使用,否则就会抛出一个 IllegalStateException 的异常。 

Spring Bean 生命周期 (熟悉)

 

1.当调用者通过 getBean(beanName)向容器请求某一个 Bean 时,如果容器注册了org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor 接口,在实例化 Bean 之前,将调用接口的 postProcessBeforeInstantiation()方法;

2.根据配置情况调用 Bean 构造函数或工厂方法实例化 Bean;

3.如果容器注册了 InstantiationAwareBeanPostProcessor 接口,在实例化 Bean 之后,调用该接口的 postProcessAfterInstantiation()方法,可在这里对已经实例化的对象进行一些“梳妆打扮”;

4.如果 Bean 配置了属性信息,容器在这一步着手将配置值设置到 Bean 对应的属性中,不过在设置每个属性之前将先调用InstantiationAwareBeanPostProcessor 接口的postProcessPropertyValues()方法;

5.调用 Bean 的属性设置方法设置属性值;

6.如果 Bean 实现了 org.springframework.beans.factory.BeanNameAware 接口,将调用setBeanName()接口方法,将配置文件中该 Bean 对应的名称设置到 Bean 中;

7.如果 Bean 实现了 org.springframework.beans.factory.BeanFactoryAware 接口,将调用 setBeanFactory()接口方法,将 BeanFactory 容器实例设置到 Bean 中;

8.如果 BeanFactory 装配了 org.springframework.beans.factory.config.BeanPostProcessor后处理器,将调用 BeanPostProcessor 的 Object postProcessBeforeInitialization(Object bean, String beanName)接口方法对 Bean 进行加工操作。其中入参 bean 是当前正在处理的 Bean,而 beanName 是当前 Bean 的配置名,返回的对象为加工处理后的 Bean。用户可以使用该方法对某些 Bean 进行特殊的处理,甚至改变 Bean 的行为, BeanPostProcessor 在 Spring 框架中占有重要的地位,为容器提供对 Bean 进行后续加工处理的切入点, Spring 容器所提供的各种“神奇功能”(如 AOP,动态代理等)都通过 BeanPostProcessor 实施;

9.如果 Bean 实现了 InitializingBean 的接口,将调用接口的 afterPropertiesSet()方法;

10.如果在<bean>通过 init-method 属性定义了初始化方法,将执行这个方法;

11.BeanPostProcessor 后处理器定义了两个方法:其一是 postProcessBeforeInitialization() 在第 8 步调用;其二是 Object postProcessAfterInitialization(Object bean, String beanName)方法,这个方法在此时调用,容器再次获得对 Bean 进行加工处理的机会;

12.如果在<bean>中指定 Bean 的作用范围为 scope=“prototype”,将 Bean 返回给调用者,调用者负责 Bean 后续生命的管理, Spring 不再管理这个 Bean 的生命周期。如果作用范围设置为 scope=“singleton”,则将 Bean 放入到 Spring IoC 容器的缓存池中,并将 Bean引用返回给调用者, Spring 继续对这些 Bean 进行后续的生命管理;

13.对于 scope=“singleton”的 Bean,当容器关闭时,将触发 Spring 对 Bean 的后续生命周期的管理工作,首先如果 Bean 实现了 DisposableBean 接口,则将调用接口的afterPropertiesSet()方法,可以在此编写释放资源、记录日志等操作;

14.对于 scope=“singleton”的 Bean,如果通过<bean>的 destroy-method 属性指定了 Bean 的销毁方法, Spring 将执行 Bean 的这个方法,完成 Bean 资源的释放等操作。

可以将这些方法大致划分为三类:

  • Bean 自身的方法:如调用 Bean 构造函数实例化 Bean,调用 Setter 设置 Bean 的属性值以及通过<bean>的 init-method 和 destroy-method 所指定的方法;
  • Bean 级生命周期接口方法:如 BeanNameAware、 BeanFactoryAware、 InitializingBean 和 DisposableBean,这些接口方法由 Bean 类直接实现;
  • 容器级生命周期接口方法:在上图中带“★” 的步骤是由 InstantiationAwareBean PostProcessor 和 BeanPostProcessor 这两个接口实现,一般称它们的实现类为“ 后处理器” 。 后处理器接口一般不由 Bean 本身实现,它们独立于 Bean,实现类以容器附加装置的形式注册到 Spring 容器中并通过接口反射为 Spring 容器预先识别。当Spring 容器创建任何 Bean 的时候,这些后处理器都会发生作用,所以这些后处理器的影响是全局性的。当然,用户可以通过合理地编写后处理器,让其仅对感兴趣Bean 进行加工处理

ApplicationContext 和 BeanFactory 另一个最大的不同之处在于:ApplicationContext会利用 Java 反射机制自动识别出配置文件中定义的 BeanPostProcessor、 InstantiationAwareBeanPostProcessor 和 BeanFactoryPostProcessor,并自动将它们注册到应用上下文中;而后者需要在代码中通过手工调用 addBeanPostProcessor()方法进行注册。这也是为什么在应用开发时,我们普遍使用 ApplicationContext 而很少使用 BeanFactory 的原因之一

Spring 后置处理器(了解)

spring的后置处理器有两类,bean后置处理器,bf(BeanFactory)后置处理器。bean后置处理器作用于bean的生命周期,bf的后置处理器作用于bean工厂的生命周期。

BF后置处理器

Spring是一个bean依赖注入容器,容器是BF,它的上级对象是applicationContxt,applicationContxt在容器功能上附加了一下新的功能。我们使用spring时,用的都是applicationConext的实现类。
我们在初始化这些applicationConext时,最终调用的都是org.springframework.context.support.AbstractApplicationContext#refresh方法用于容器的初始化。这个方法是一个模板方法,规定了容器实例化的步骤。其中BF初始化完成后,会调用BF的后置处理器对BF进行后置处理。而后置处理器的调用是在org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors这个方法中进行的。

BF的后置处理器有两种,BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor。BF后置处理器和BD(BeanDefinition)后置处理器。BD后置处理器是BF后置处理器的子接口。

invokeBeanFactoryPostProcessors方法内,调用后置处理器的步骤如下:

  1. 通过ac(applicationContext)的addBeanFactoryPostProcessor方法加入的BD后置处理器,按照加入顺序调用。
  2. ac中类型为bd后置处理器的,拥有PriorityOrdered接口的BD后置处理器。
  3. ac中类型为bd后置处理器的,拥有Ordered接口的BD后置处理器。
  4. ac中类型为bd后置处理器的,PriorityOrdered和Ordered接口都没实现的BeanDefinitionRegistryPostProcessor
  5. ac中类型为bf后置处理器的,拥有PriorityOrdered接口的BF后置处理器。
  6. ac中类型为bf后置处理器的,拥有Ordered接口的BF后置处理器。
  7. ac中类型为bf后置处理器的,PriorityOrdered和Ordered接口都没实现的BeanFactoryPostProcessor

在AnnotationConfigApplicationContext上下文中,会有一个关键的BD后置处理器:ConfigurationClassPostProcessor。它是用来扫描所有交给spring管理的注解类的。将其解析为BD实例放到AC中去。

Bean后置处理器

spring管理的是bean,所以bean的实例化是一个重要的过程。spring是通过org.springframework.beans.factory.support.AbstractBeanFactory#getBean方法实例化并初始化Bean。实例化Bean的过程中,可以通过bean的后置处理器插手Bean的实例化过程。

Bean的实例化过程如下:

  1. 解析BD
  2. 确定构造方法
  3. 用构造方法实例化(构造方法依赖不能进行循环依赖)
  4. 属性注入
  5. 初始化

在这个过程中,spring内设了8个bean的后置处理器调用点,用来进行扩展。

Bean后置处理器有五种:

  1. BeanPostProcessor:基本后置处理器,有两个方法,分别在Bean初始化前后调用
    • postProcessBeforeInitialization在初始化之前调用
    • postProcessAfterInitialization在初始化之后调用
  2. DestructionAwareBeanPostProcessor在bean被摧毁的时候调用
  3. InstantiationAwareBeanPostProcessor:有三个方法,
    • postProcessBeforeInstantiation在最开始调用,如果返回Bean实例不为空,直接调用BeanPostProcessor的postProcessAfterInitialization方法,返回该bean,不在进行其他动作。
    • postProcessAfterInstantiation判断是否需要进行属性填充
    • postProcessPropertyValues,进行属性填充前,处理bean的PropertyValues。用于属性填充
  4. MergedBeanDefinitionPostProcessor,只有一个方法,postProcessMergedBeanDefinition,在实例化之前,对BD进行后置处理。
  5. SmartInstantiationAwareBeanPostProcessor,有三个方法
    • predictBeanType,预测InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation方法返回的类型。
    • determineCandidateConstructors,确定该bd的构造函数,找不到用默认的构造函数
    • getEarlyBeanReference,在需要获取earlyBean时,在返回earlyBean前对earlyBean进行后置处理。

Spring Bean 继承 (了解)

在 Spring 中,Bean 和 Bean 之间也存在继承关系。我们将被继承的 Bean 称为父 Bean,将继承父 Bean 配置信息的 Bean 称为子 Bean。

Spring Bean 的定义中可以包含很多配置信息,例如构造方法参数、属性值。子 Bean 既可以继承父 Bean 的配置数据,也可以根据需要重写或添加属于自己的配置信息。

在 Spring XML 配置中,我们通过子 Bean 的 parent 属性来指定需要继承的父 Bean,配置格式如下。

<!--父Bean-->
<bean id="parentBean" class="xxx.xxxx.xxx.ParentBean" >
    <property name="xxx" value="xxx"></property>
    <property name="xxx" value="xxx"></property>
</bean> 
<!--子Bean--> 
<bean id="childBean" class="xxx.xxx.xxx.ChildBean" parent="parentBean"></bean>

在父 Bean 的定义中,有一个十分重要的属性,那就是 abstract 属性。如果一个父 Bean 的 abstract 属性值为 true,则表明这个 Bean 是抽象的。

抽象的父 Bean 只能作为模板被子 Bean 继承,它不能实例化,也不能被其他 Bean 引用,更不能在代码中根据 id 调用 getBean() 方法获取,否则就会返回错误。

在父 Bean 的定义中,既可以指定 class 属性,也可以不指定 class 属性。如果父 Bean 定义没有明确地指定 class 属性,那么这个父 Bean 的 abstract 属性就必须为 true。

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <bean id="animal" abstract="true">
        <property name="name" value="动物"></property>
        <property name="age" value="10"></property>
    </bean>

    <bean id="dog" class="net.biancheng.c.Dog" parent="animal">
        <property name="name" value="小狗"></property>
        <property name="call" value="汪汪汪……"></property>
    </bean>
</beans>

Spring 依赖注入DI(熟悉)

DI(Dependency Injection),即“依赖注入”,在系统运行中,IoC容器动态的向某个对象提供它所依赖的其他对象,也就是根据依赖关系把某个对象所依赖的其他对象给自动注入到这个对象中,这也就是为什么叫依赖注入,DI的实现的通过反射来实现的。

DI其他描述

依赖注入(Dependency Injection),即组件之间的依赖关系由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中

理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:

  • 谁依赖于谁:当然是应用程序依赖于IoC容器;
  • 为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;
  • 谁注入谁:很明显是IoC容器注入应用程序某个对象所依赖的对象;
  • 注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。

IoC和DI由什么关系呢?

其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊,只能看出容器控制对象这一个层面,很难让人看出谁来维护对象关系,所以后面引用依赖注入,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置的依赖对象”。

总的来说,其实是息息相关的关系,Ioc的实现离不开Di,Di的实现又要依赖于ioc。

Ioc的实现离不开Di是指的Ioc在管理对象之间的依赖关系的时候需要通过Di来实现依赖注入,把一个对象所依赖的其他对象注入到这个对象中来

Di的实现又要依赖于ioc是指di能够把一个对象所依赖的其他对象注入到这个对象中来是因为对象的控制权已经转交给了ioc容器,所以di的实现是依赖于ioc的

举个栗子

比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。

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

构造器注入

public class AccountServiceImpl implements IAccountService {
 
    //如果是经常变化的数据,并不适用于注入的方式
    private String name;
    private Integer age;
    private Date birthday;
 
    public AccountServiceImpl(String name,Integer age,Date birthday){
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }
 
    public void  saveAccount(){
        System.out.println("service中的saveAccount方法执行了。。。"+name+","+age+","+birthday);
    }
 
 
}

构造函数注入

使用的标签:constructor-arg
标签出现的位置:bean标签的内部
标签中的属性

  • type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
  • index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置是从0开始
  • name:用于指定给构造函数中指定名称的参数赋值
  • value:用于提供基本类型和String类型的数据
  • ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
  <bean id="accountService" class="com.lp.service.impl.AccountServiceImpl">
        <constructor-arg name="name" value="泰斯特"></constructor-arg>
        <constructor-arg name="age" value="18"></constructor-arg>
        <constructor-arg name="birthday" ref="now"></constructor-arg>
    </bean>
 
    <!-- 配置一个日期对象 -->
    <bean id="now" class="java.util.Date"></bean>

构造器注入优缺点

  • 优势:在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功。
  • 弊端:改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供。

Set方法注入

public class AccountServiceImpl2 implements IAccountService {
 
    //如果是经常变化的数据,并不适用于注入的方式
    private String name;
    private Integer age;
    private Date birthday;
 
    public void setName(String name) {
        this.name = name;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    public void  saveAccount(){
        System.out.println("service中的saveAccount方法执行了。。。"+name+","+age+","+birthday);
    }
 
}

set方法注入

涉及的标签:property
出现的位置:bean标签的内部
标签的属性

  • name:用于指定注入时所调用的set方法名称
  • value:用于提供基本类型和String类型的数据
  • ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
 <!-- 配置一个日期对象 -->
    <bean id="now" class="java.util.Date"></bean>
    <bean id="accountService2" class="com.lp.service.impl.AccountServiceImpl2">
        <property name="name" value="TEST" ></property>
        <property name="age" value="21"></property>
        <property name="birthday" ref="now"></property>
    </bean>

 Set方法注入优缺点

  • 优势:创建对象时没有明确的限制,可以直接使用默认构造函数
  • 弊端:如果有某个成员必须有值,则获取对象是有可能set方法没有执行。

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

构造函数注入

setter注入

没有部分注入

有部分注入

不会覆盖setter 属性

会覆盖setter 属性

任意修改都会创建一个新实例

任意修改不会创建一个新实例

适用于设置很多属性

适用于设置少量属性

解释上面的"会不会覆盖setter 属性"

Setter注入将覆盖构造函数注入。如果我们同时使用构造函数和setter注入, 则IOC容器将使用setter注入。两种依赖方式都可以使用,构造器注入和Setter方法注入。最好的解决方案是用构造器参数实现强制依赖,setter方法实现可选依赖。

基于注解的注入(常用)

使用@Autowired或@Resource注解来自动装配指定的bean,在使用@Autowired或@Resource注解之前需要在Spring配置文件进行配置,<context:annotation-config />。在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。

@Resource

@Resource不属于spring的注解,而是Java的注解,它来自于JSR-250位于java.annotation包下,使用该annotation为目标bean指定协作者Bean。

public class AnotationExp {
 
    @Resource(name = "HappyClient")
    private HappyClient happyClient;
    
    @Resource(type = HappyPlayAno.class)
    private HappyPlayAno happyPlayAno;
}

@Autowired

@Autowired属于Spring 的org.springframework.beans.factory.annotation包下,可用于为类的属性、构造器、方法进行注值。

@Controller
public class HappyController {
    @Autowired //默认依赖的ClubDao 对象(Bean)必须存在
    //@Autowired(required = false) 改变默认方式
    @Qualifier("goodClubService")
    private ClubService clubService;
 
    // Control the people entering the Club
    // do something
}

*Spring 注入内部 Bean (了解)

Spring 注入集合(了解)

Spring提供了以下四种集合类的配置元素
1、list 该标签用来装配可重复的list值(list-value标签)
2、set 该标签用来装配没有重复的set值(set-value标签)
3、map 该标签可用来注入键和值可以为任何类型的键值对(map-entry标签)
4、props 该标签支持注入键和值都是字符串类型的键值对

package com.model;
 
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
 
public class Programmer {
    private List<String> cars;
    private Set<String> pats; //宠物
    private Map<String,String> infos; //信息
    private Properties mysqlInfos; //mysql数据库链接信息
    private String[] numbers; //家庭成员
    
    public List<String> getCars() {
        return cars;
    }
    
    public void setCars(List<String> cars) {
        this.cars = cars;
    }
    
    public Set<String> getPats() {
        return pats;
    }
    
    public void setPats(Set<String> pats) {
        this.pats = pats;
    }
    
    public Map<String, String> getInfos() {
        return infos;
    }
    
    public void setInfos(Map<String, String> infos) {
        this.infos = infos;
    }
    
    public Properties getMysqlInfos() {
        return mysqlInfos;
    }
    
    public void setMysqlInfos(Properties mysqlInfos) {
        this.mysqlInfos = mysqlInfos;
    }
    
    public String[] getNumbers() {
        return numbers;
    }
    
    public void setNumbers(String[] numbers) {
        this.numbers = numbers;
    }
}
 
<?xml version="1.0" encoding="utf-8"?>
 
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop
                           http://www.springframework.org/schema/aop/spring-aop.xsd
                           http://www.springframework.org/schema/tx
                           http://www.springframework.org/schema/tx/spring-tx.xsd">
  
  <!--
    集合注入
    -->
  <bean id="programmer" class="com.model.Programmer">
    <property name="cars">
      <!-- 1.  list数据注入 //有序集合-->
      <list>
        <value>ofo</value>
        <value>mobai</value>
        <value>宝马</value>
      </list>
    </property>
    
    <property name="pats">
      <!-- 2. set数据注入 //无序集合-->
      <set>
        <value>小黑</value>
        <value>小红</value>
        <value>小白</value>
      </set>
    </property>
    
    <property name="infos">
      <!-- 3. map数据注入 -->
      <map>
        <entry key="name" value="cjx"></entry>
        <entry key="age" value="23"></entry>
        <entry key="id" value="20821111355"></entry>
      </map>
    </property>
    
    <property name="mysqlInfos">
      <!-- 4. properties数据注入  //实际也是set类型是无序的-->
      <props>
        <prop key="url">mysql:jdbc://localhost:3306/dbname</prop>
        <prop key="user">root</prop>
        <prop key="password">123456</prop>
      </props>
    </property>
    
    <property name="numbers">
      <!-- 5. 数组的数据注入 -->
      <array>
        <value>哥哥</value>
        <value>弟弟</value>
        <value>妹妹</value>
        <value>姐姐</value>
      </array>
    </property>
  </bean>
  
  
</beans>
public class Lesson4 {
    
    @Test
    public void test() throws Exception{
        
        /*
        * bean的集合注入
        * */
        ApplicationContext context = new ClassPathXmlApplicationContext("beans4.xml");
        Programmer programmer = (Programmer) context.getBean("programmer");
        
        
        System.out.println("车:"+programmer.getCars());
        System.out.println("宠物:"+programmer.getPats());
        System.out.println("信息:"+programmer.getInfos());
        System.out.println("数据库连接信息::"+programmer.getMysqlInfos());
        System.out.println("家庭成员:");
        //家庭成员是数组类型,需要遍历
        for (String number: programmer.getNumbers()){
            System.out.println(number);
        }
    }
}

运行结果 

Spring Bean 自动装配 (了解)

Spring 的自动装配功能可以让 Spring 容器依据某种规则(自动装配的规则,有五种),为指定的 Bean 从应用的上下文(AppplicationContext 容器)中查找它所依赖的 Bean,并自动建立 Bean 之间的依赖关系。而这一过程是在完全不使用任何 <constructor-arg>和 <property> 元素 ref 属性的情况下进行的。

Spring 的自动装配功能能够有效地简化 Spring 应用的 XML 配置,因此在配置数量相当多时采用自动装配降低工作量。

Spring 框架式默认不支持自动装配的,要想使用自动装配,则需要对 Spring XML 配置文件中 <bean> 元素的 autowire 属性进行设置。

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <!--部门 Dept 的 Bean 定义-->
    <bean id="dept" class="net.biancheng.c.Dept"></bean>
   
    <!--雇员 Employee 的 Bean 定义,通过 autowire 属性设置自动装配的规则-->
    <bean id="employee" class="net.biancheng.c.Employee" autowire="byName">
    </bean>
</beans>
package net.biancheng.c;

public class Employee {
    //员工编号
    private String empNo;
    //员工姓名
    private String empName;
    //部门信息
    private Dept dept;

    public Employee() {
        System.out.println("正在执行 Employee 的无参构造方法>>>>");
    }

    public Employee(String empNo, String empName, Dept dept) {
        System.out.println("正在执行 Employee 的有参构造方法>>>>");
        this.empNo = empNo;
        this.empName = empName;
        this.dept = dept;
    }

    public void setEmpNo(String empNo) {
        System.out.println("正在执行 Employee 的 setEmpNo 方法>>>>");
        this.empNo = empNo;
    }

    public void setEmpName(String empName) {
        System.out.println("正在执行 Employee 的 setEmpName 方法>>>>");
        this.empName = empName;
    }

    public void setDept(Dept dept) {
        System.out.println("正在执行 Employee 的 setDept 方法>>>>");
        this.dept = dept;
    }

    public Dept getDept() {
        return dept;
    }

    public String getEmpNo() {
        return empNo;
    }

    public String getEmpName() {
        return empName;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "empNo='" + empNo + '\'' +
                ", empName='" + empName + '\'' +
                ", dept=" + dept +
                '}';
    }
}

Spring 共提供了 5 中自动装配规则,它们分别与 autowire 属性的 5 个取值对应,具体说明如下表。

属性值说明
byName按名称自动装配。

Spring 会根据的 Java 类中对象属性的名称,在整个应用的上下文 ApplicationContext(IoC 容器)中查找。若某个 Bean 的 id 或 name 属性值与这个对象属性的名称相同,则获取这个 Bean,并与当前的 Java 类 Bean 建立关联关系。
byType按类型自动装配。

Spring 会根据 Java 类中的对象属性的类型,在整个应用的上下文 ApplicationContext(IoC 容器)中查找。若某个 Bean 的 class 属性值与这个对象属性的类型相匹配,则获取这个 Bean,并与当前的 Java 类的 Bean 建立关联关系。
constructor与 byType 模式相似,不同之处在与它应用于构造器参数(依赖项),如果在容器中没有找到与构造器参数类型一致的 Bean,那么将抛出异常。

其实就是根据构造器参数的数据类型,进行 byType 模式的自动装配。
default表示默认采用上一级元素 <beans> 设置的自动装配规则(default-autowire)进行装配。
no默认值,表示不使用自动装配,Bean 的依赖关系必须通过 <constructor-arg>和 <property> 元素的 ref 属性来定义。

Spring 基于注解装配 Bean (熟悉)

从 Java 5 开始,Java 增加了对注解(Annotation)的支持,它是代码中的一种特殊标记,可以在编译、类加载和运行时被读取,执行相应的处理。开发人员可以通过注解在不改变原有代码和逻辑的情况下,在源代码中嵌入补充信息。

Spring 从 2.5 版本开始提供了对注解技术的全面支持,我们可以使用注解来实现自动装配,简化 Spring 的 XML 配置。

Spring 通过注解实现自动装配的步骤如下:

  1. 引入依赖
  2. 开启组件扫描
  3. 使用注解定义 Bean
  4. 依赖注入

1. 引入依赖

使用注解的第一步,就是要在项目中引入以下 Jar 包。

  • org.springframework.core-5.3.13.jar
  • org.springframework.beans-5.3.13.jar
  • spring-context-5.3.13.jar
  • spring-expression-5.3.13.jar
  • commons.logging-1.2.jar
  • spring-aop-5.3.13.jar

注意,除了 spring 的四个基础 jar 包和 commons-logging-xxx.jar 外,想要使用注解实现 Spring 自动装配,还需要引入Spring 提供的 spring-aop 的 Jar 包

2. 开启组件扫描

Spring 默认不使用注解装配 Bean,因此我们需要在 Spring 的 XML 配置中,通过 <context:component-scan> 元素开启 Spring Beans的自动扫描功能。开启此功能后,Spring 会自动从扫描指定的包(base-package 属性设置)及其子包下的所有类,如果类上使用了 @Component 注解,就将该类装配到容器中。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
    <!--开启组件扫描功能-->
    <context:component-scan base-package="net.biancheng.c"></context:component-scan>
</beans>

注意:在使用  <context:component-scan> 元素开启自动扫描功能前,首先需要在 XML 配置的一级标签 <beans> 中添加 context 相关的约束。

3. 使用注解定义 Bean

Spring 提供了以下多个注解,这些注解可以直接标注在 Java 类上,将它们定义成 Spring Bean。 

注解说明
@Component该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。

使用时只需将该注解标注在相应类上即可。
@Repository该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Service该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Controller该注解通常作用在控制层(如 Struts2 的 Action、SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。

4. 基于注解方式实现依赖注入

我们可以通过以下注解将定义好 Bean 装配到其它的 Bean 中。

注解说明
@Autowired可以应用到 Bean 的属性变量、setter 方法、非 setter 方法及构造函数等,默认按照 Bean 的类型进行装配。

@Autowired 注解默认按照 Bean 的类型进行装配,默认情况下它要求依赖对象必须存在,如果允许 null 值,可以设置它的 required 属性为 false。如果我们想使用按照名称(byName)来装配,可以结合 @Qualifier 注解一起使用
@Resource作用与 Autowired 相同,区别在于 @Autowired 默认按照 Bean 类型装配,而 @Resource 默认按照 Bean 的名称进行装配。

@Resource 中有两个重要属性:name 和 type。
  • Spring 将 name 属性解析为 Bean 的实例名称,type 属性解析为 Bean 的实例类型。
  • 如果指定 name 属性,则按实例名称进行装配;
  • 如果指定 type 属性,则按 Bean 类型进行装配;
  • 如果都不指定,则先按 Bean 实例名称装配,如果不能匹配,则再按照 Bean 类型进行装配;如果都无法匹配,则抛出 NoSuchBeanDefinitionException 异常。
@Qualifier与 @Autowired 注解配合使用,会将默认的按 Bean 类型装配修改为按 Bean 的实例名称装配,Bean 的实例名称由 @Qualifier 注解的参数指定。

Spring AOP (熟悉)

Spring AOP 是 Spring 框架的核心模块之一,它使用纯 Java 实现,因此不需要专门的编译过程和类加载器,可以在程序运行期通过代理方式向目标类织入增强代码。

Spring AOP 的代理机制

Spring 在运行期会为目标对象生成一个动态代理对象,并在代理对象中实现对目标对象的增强。
Spring AOP 的底层是通过以下 2 种动态代理机制,为目标对象(Target Bean)执行横向织入的。

代理技术描述
JDK 动态代理Spring AOP 默认的动态代理方式,若目标对象实现了若干接口,Spring 使用 JDK 的 java.lang.reflect.Proxy 类进行代理。
CGLIB 动态代理若目标对象没有实现任何接口,Spring 则使用 CGLIB 库生成目标对象的子类,以实现对目标对象的代理。

注意:由于被标记为 final 的方法是无法进行覆盖的,因此这类方法不管是通过 JDK 动态代理机制还是 CGLIB 动态代理机制都是无法完成代理的。

Spring AOP 连接点

Spring AOP 并没有像其他 AOP 框架(例如 AspectJ)一样提供了完成的 AOP 功能,它是 Spring 提供的一种简化版的 AOP 组件。其中最明显的简化就是,Spring AOP 只支持一种连接点类型:方法调用。您可能会认为这是一个严重的限制,但实际上 Spring AOP 这样设计的原因是为了让  Spring 更易于访问。

方法调用连接点是迄今为止最有用的连接点,通过它可以实现日常编程中绝大多数与 AOP 相关的有用的功能。如果需要使用其他类型的连接点(例如成员变量连接点),我们可以将 Spring AOP 与其他的 AOP 实现一起使用,最常见的组合就是 Spring AOP + ApectJ。 

Spring AOP 通知类型

AOP 联盟为通知(Advice)定义了一个 org.aopalliance.aop.Interface.Advice 接口。
Spring AOP 按照通知(Advice)织入到目标类方法的连接点位置,为 Advice 接口提供了 6 个子接口,如下表。

通知类型接口描述
前置通知 org.springframework.aop.MethodBeforeAdvice在目标方法执行前实施增强。
后置通知org.springframework.aop.AfterAdvice在目标方法执行后实施增强。
后置返回通知org.springframework.aop.AfterReturningAdvice在目标方法执行完成,并返回一个返回值后实施增强。
环绕通知org.aopalliance.intercept.MethodInterceptor在目标方法执行前后实施增强。
异常通知org.springframework.aop.ThrowsAdvice在方法抛出异常后实施增强。
引入通知org.springframework.aop.IntroductionInterceptor在目标类中添加一些新的方法和属性。

Spring AOP 切面类型

Spring 使用 org.springframework.aop.Advisor 接口表示切面的概念,实现对通知(Adivce)和连接点(Joinpoint)的管理。

在 Spring AOP 中,切面可以分为三类:一般切面、切点切面和引介切面。

切面类型接口描述
一般切面org.springframework.aop.AdvisorSpring AOP 默认的切面类型。

由于 Advisor 接口仅包含一个 Advice(通知)类型的属性,而没有定义 PointCut(切入点),因此它表示一个不带切点的简单切面。

这样的切面会对目标对象(Target)中的所有方法进行拦截并织入增强代码。由于这个切面太过宽泛,因此我们一般不会直接使用。
切点切面org.springframework.aop.PointcutAdvisorAdvisor 的子接口,用来表示带切点的切面,该接口在 Advisor 的基础上还维护了一个 PointCut(切点)类型的属性。

使用它,我们可以通过包名、类名、方法名等信息更加灵活的定义切面中的切入点,提供更具有适用性的切面。
引介切面org.springframework.aop.IntroductionAdvisorAdvisor 的子接口,用来代表引介切面,引介切面是对应引介增强的特殊的切面,它应用于类层面上,所以引介切面适用 ClassFilter 进行定义。

一般切面的 AOP 开发

当我们在使用 Spring AOP 开发时,若没有对切面进行具体定义,Spring AOP 会通过 Advisor 为我们定义一个一般切面(不带切点的切面),然后对目标对象(Target)中的所有方法连接点进行拦截,并织入增强代码。

示例 1

下面我们就通过一个简单的实例演示下一般切面的 AOP 开发流程。

1. 创建一个名为 my-spring-aop-demo 的 Java 工程,并将以下依赖引入到工程中。

  • org.springframework.core-5.3.13.jar
  • org.springframework.beans-5.3.13.jar
  • spring-context-5.3.13.jar
  • spring-expression-5.3.13.jar
  • commons.logging-1.2.jar
  • spring-aop-5.3.13.jar

2. 在 net.biancheng.c.dao 包下,创建一个名为 UserDao 的接口,代码如下。

package net.biancheng.c.dao;
public interface UserDao {
    public void add();
    public void delete();
    public void modify();
    public void get();
}

3. 在 net.biancheng.c.dao.impl 包下,创建 UserDao 的实现类 UserDaoImpl,代码如下。

package net.biancheng.c.dao.impl;

import net.biancheng.c.dao.UserDao;

public class UserDaoImpl implements UserDao {
    @Override
    public void add() {
        System.out.println("正在执行 UserDao 的 add() 方法……");
    }

    @Override
    public void delete() {
        System.out.println("正在执行 UserDao 的 delete() 方法……");
    }

    @Override
    public void modify() {
        System.out.println("正在执行 UserDao 的 modify() 方法……");
    }

    @Override
    public void get() {
        System.out.println("正在执行 UserDao 的 get() 方法……");
    }
}

 4. 在 net.biancheng.c.advice 包下,创建一个名为 UserDaoBeforeAdvice 的前置增强类,代码如下。

package net.biancheng.c.advice;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
/**
* 增强代码
* MethodBeforeAdvice 前置增强
*
* @author c语言中文网 c.biancheng.net
*/
public class UserDaoBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("正在执行前置增强操作…………");
    }
}

5. 在 src 目录下创建一个 Spring 的配置文件 Beans.xml,配置内容如下。 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">

    <!--******Advisor : 代表一般切面,Advice 本身就是一个切面,对目标类所有方法进行拦截(* 不带有切点的切面.针对所有方法进行拦截)*******-->
    <!-- 定义目标(target)对象 -->
    <bean id="userDao" class="net.biancheng.c.dao.impl.UserDaoImpl"></bean>

    <!-- 定义增强 -->
    <bean id="beforeAdvice" class="net.biancheng.c.advice.UserDaoBeforeAdvice"></bean>

    <!--通过配置生成代理 UserDao 的代理对象 -->
    <bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 设置目标对象 -->
        <property name="target" ref="userDao"/>
        <!-- 设置实现的接口 ,value 中写接口的全路径 -->
        <property name="proxyInterfaces" value="net.biancheng.c.dao.UserDao"/>
        <!-- 需要使用value:增强 Bean 的名称 -->
        <property name="interceptorNames" value="beforeAdvice"/>
    </bean>

</beans>

Spring 能够基于 org.springframework.aop.framework.ProxyFactoryBean 类,根据目标对象的类型(是否实现了接口)自动选择使用 JDK 动态代理或 CGLIB 动态代理机制,为目标对象(Target Bean)生成对应的代理对象(Proxy Bean)。

ProxyFactoryBean 的常用属性如下表所示。

属性描述
target 需要代理的目标对象(Bean)
proxyInterfaces代理需要实现的接口,如果需要实现多个接口,可以通过 <list> 元素进行赋值。
proxyTargetClass针对类的代理,该属性默认取值为 false(可省略), 表示使用 JDK 动态代理;取值为 true,表示使用 CGlib 动态代理
interceptorNames拦截器的名字,该属性的取值既可以是拦截器、也可以是 Advice(通知)类型的 Bean,还可以是切面(Advisor)的 Bean。
singleton返回的代理对象是否为单例模式,默认值为 true。
optimize 是否对创建的代理进行优化(只适用于CGLIB)

6. 在 net.biancheng.c 包下,创建一个名为 MainApp 的类,代码如下。 

package net.biancheng.c;

import net.biancheng.c.dao.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {

    public static void main(String[] args) {
        //获取 ApplicationContext 容器
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        //获取代理对象
        UserDao userDao = context.getBean("userDaoProxy", UserDao.class);
        //调用 UserDao 中的各个方法
        userDao.add();
        userDao.delete();
        userDao.get();
        userDao.modify();
    }
}

7. 执行 MainApp 中的 main 方法,控制台输出如下。

正在执行前置增强操作…………
正在执行 UserDao 的 add() 方法……
正在执行前置增强操作…………
正在执行 UserDao 的 delete() 方法……
正在执行前置增强操作…………
正在执行 UserDao 的 get() 方法……
正在执行前置增强操作…………
正在执行 UserDao 的 modify() 方法……

从控制台输出可以看出,UserDao 接口中的所有方法都被增强了。 

基于 PointcutAdvisor 的 AOP 开发

PointCutAdvisor 是 Adivsor 接口的子接口,用来表示带切点的切面。使用它,我们可以通过包名、类名、方法名等信息更加灵活的定义切面中的切入点,提供更具有适用性的切面。

Spring 提供了多个 PointCutAdvisor 的实现,其中常用实现类如如下。

  • NameMatchMethodPointcutAdvisor:指定 Advice 所要应用到的目标方法名称,例如 hello* 代表所有以 hello 开头的所有方法。
  • RegExpMethodPointcutAdvisor:使用正则表达式来定义切点(PointCut),RegExpMethodPointcutAdvisor 包含一个 pattern 属性,该属性使用正则表达式描述需要拦截的方法。

示例 2

下面我们就通过一个简单的实例,演示下切点切面的 AOP 开发。

1. 在 my-spring-aop-demo 的 net.biacheng.c.dao 包下,创建一个名为 OrderDao,代码如下。

package net.biancheng.c.dao;

public class OrderDao {

    public void add() {
        System.out.println("正在执行 UserDao 的 add() 方法……");
    }

    public void adds() {
        System.out.println("正在执行 UserDao 的 adds() 方法……");
    }

    public void delete() {
        System.out.println("正在执行 UserDao 的 delete() 方法……");
    }

    public void modify() {
        System.out.println("正在执行 UserDao 的 modify() 方法……");
    }

    public void get() {
        System.out.println("正在执行 UserDao 的 get() 方法……");
    }
}

2. 在 net.biancheng.c.advice 包下,创建一个名为 OrderDaoAroundAdvice 的环绕增强类,代码如下。

package net.biancheng.c.advice;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

/**
* 增强代码
* 环绕增强
*
* @author c语言中文网 c.biancheng.net
*/
public class OrderDaoAroundAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("环绕增强前********");
        //执行被代理对象中的逻辑
        Object result = methodInvocation.proceed();
        System.out.println("环绕增强后********");
        return result;
    }
}

 3. 在 Beans.xml 中添加以下配置。

<!--带切点的切面-->
<!-- 定义目标(target)对象 -->
<bean id="orderDao" class="net.biancheng.c.dao.OrderDao"></bean>

<!-- 定义增强 -->
<bean id="aroundAdvice" class="net.biancheng.c.advice.OrderDaoAroundAdvice"></bean>

<!--定义切面-->
<bean id="myPointCutAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    <!--定义表达式,规定哪些方法进行拦截 .* 表示所有方法-->
    <!--<property name="pattern" value=".*"></property>-->
    <property name="patterns" value="net.biancheng.c.dao.OrderDao.add.*,net.biancheng.c.dao.OrderDao.delete.*"></property>
    <property name="advice" ref="aroundAdvice"></property>
</bean>

<!--Spring 通过配置生成代理-->
<bean id="orderDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    <!-- 配置目标 -->
    <property name="target" ref="orderDao"></property>
    <!-- 针对类的代理,该属性默认取值为 false(可省略), 表示使用 JDK 动态代理;取值为 true,表示使用 CGlib 动态代理-->
    <property name="proxyTargetClass" value="true"></property>
    <!-- 在目标上应用增强 -->
    <property name="interceptorNames" value="myPointCutAdvisor"></property>
</bean>

4. 修改 MainApp 类 main 方法的代码。

package net.biancheng.c;

import net.biancheng.c.dao.OrderDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {

    public static void main(String[] args) {
        //获取 ApplicationContext 容器
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        //获取代理对象
        OrderDao orderDao = context.getBean("orderDaoProxy", OrderDao.class);
        //调用 OrderDao 中的各个方法
        orderDao.add();
        orderDao.adds();
        orderDao.delete();
        orderDao.get();
        orderDao.modify();
    }
}

 5. 执行 MainApp 中的 main() 方法,控制台输出如下。

环绕增强前********
正在执行 OrderDao 的 add() 方法……
环绕增强后********
环绕增强前********
正在执行 OrderDao 的 adds() 方法……
环绕增强后********
环绕增强前********
正在执行 OrderDao 的 delete() 方法……
环绕增强后********
正在执行 OrderDao 的 get() 方法……
正在执行 OrderDao 的 modify() 方法……

自动代理

在前面的案例中,所有目标对象(Target Bean)的代理对象(Proxy Bean)都是在 XML 配置中通过 ProxyFactoryBean 创建的。但在实际开发中,一个项目中往往包含非常多的 Bean, 如果每个 Bean 都通过 ProxyFactoryBean 创建,那么开发和维护成本会十分巨大。为了解决这个问题,Spring 为我们提供了自动代理机制。

Spring 提供的自动代理方案,都是基于后处理 Bean 实现的,即在 Bean 创建的过程中完成增强,并将目标对象替换为自动生成的代理对象。通过 Spring 的自动代理,我们在程序中直接拿到的 Bean 就已经是 Spring 自动生成的代理对象了。

Spring 为我们提供了 3 种自动代理方案:

  • BeanNameAutoProxyCreator:根据 Bean 名称创建代理对象。
  • DefaultAdvisorAutoProxyCreator:根据 Advisor 本身包含信息创建代理对象。
  • AnnotationAwareAspectJAutoProxyCreator:基于 Bean 中的 AspectJ 注解进行自动代理对象。

根据 Bean 名称创建代理对象(BeanNameAutoProxyCreator)

1. 在 my-spring-aop-demo 工程的 src 目录下,创建一个名为 Beans2.xml 的配置文件,配置内容如下。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 定义目标(target)对象 -->
    <bean id="userDao" class="net.biancheng.c.dao.impl.UserDaoImpl"></bean>
    <bean id="orderDao" class="net.biancheng.c.dao.OrderDao"></bean>

    <!-- 定义增强 -->
    <bean id="beforeAdvice" class="net.biancheng.c.advice.UserDaoBeforeAdvice"></bean>
    <bean id="aroundAdvice" class="net.biancheng.c.advice.OrderDaoAroundAdvice"></bean>

    <!--Spring 自动代理:根据 Bean 名称创建代理独享-->
    <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
        <property name="beanNames" value="*Dao"></property>
        <property name="interceptorNames" value="beforeAdvice,aroundAdvice"></property>
    </bean>

</beans>

 2. 修改 MainApp 中 main 方法,代码如下。

package net.biancheng.c;

import net.biancheng.c.dao.OrderDao;
import net.biancheng.c.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {

    public static void main(String[] args) {
        //获取 ApplicationContext 容器
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans2.xml");
        //获取代理对象
        UserDao userDao = context.getBean("userDao", UserDao.class);
        //获取代理对象
        OrderDao orderDao = context.getBean("orderDao", OrderDao.class);

        //调用 UserDao 中的各个方法
        userDao.add();
        userDao.delete();
        userDao.modify();
        userDao.get();

        //调用 OrderDao 中的各个方法
        orderDao.add();
        orderDao.adds();
        orderDao.delete();
        orderDao.get();
        orderDao.modify();
    }
}

3. 执行 MainApp 中的 main() 方法,控制台输出如下。

正在执行前置增强操作…………
环绕增强前********
正在执行 UserDao 的 add() 方法……
环绕增强后********
正在执行前置增强操作…………
环绕增强前********
正在执行 UserDao 的 delete() 方法……
环绕增强后********
正在执行前置增强操作…………
环绕增强前********
正在执行 UserDao 的 modify() 方法……
环绕增强后********
正在执行前置增强操作…………
环绕增强前********
正在执行 UserDao 的 get() 方法……
环绕增强后********
正在执行前置增强操作…………
环绕增强前********
正在执行 OrderDao 的 add() 方法……
环绕增强后********
正在执行前置增强操作…………
环绕增强前********
正在执行 OrderDao 的 adds() 方法……
环绕增强后********
正在执行前置增强操作…………
环绕增强前********
正在执行 OrderDao 的 delete() 方法……
环绕增强后********
正在执行前置增强操作…………
环绕增强前********
正在执行 OrderDao 的 get() 方法……
环绕增强后********
正在执行前置增强操作…………
环绕增强前********
正在执行 OrderDao 的 modify() 方法……
环绕增强后********

根据切面中信息创建代理对象(DefaultAdvisorAutoProxyCreator)

1. 修改 Beans2.xml 中的配置,配置内容如下。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 定义目标(target)对象 -->
    <bean id="userDao" class="net.biancheng.c.dao.impl.UserDaoImpl"></bean>
    <bean id="orderDao" class="net.biancheng.c.dao.OrderDao"></bean>

    <!-- 定义增强 -->
    <bean id="beforeAdvice" class="net.biancheng.c.advice.UserDaoBeforeAdvice"></bean>
    <bean id="aroundAdvice" class="net.biancheng.c.advice.OrderDaoAroundAdvice"></bean>

    <!--定义切面-->
    <bean id="myPointCutAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <!--定义表达式,规定哪些方法进行拦截 .* 表示所有方法-->
        <!--<property name="pattern" value=".*"></property>-->
        <property name="patterns"
                  value="net.biancheng.c.dao.OrderDao.add.*,net.biancheng.c.dao.OrderDao.delete.*"></property>
        <property name="advice" ref="aroundAdvice"></property>
    </bean>

    <!--Spring 自动代理:根据切面 myPointCutAdvisor 中信息创建代理对象-->
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>

</beans>

2. 执行 MainApp 中的 main() 方法,控制台输出如下。 

正在执行 UserDao 的 add() 方法……
正在执行 UserDao 的 delete() 方法……
正在执行 UserDao 的 modify() 方法……
正在执行 UserDao 的 get() 方法……
环绕增强前********
正在执行 OrderDao 的 add() 方法……
环绕增强后********
环绕增强前********
正在执行 OrderDao 的 adds() 方法……
环绕增强后********
环绕增强前********
正在执行 OrderDao 的 delete() 方法……
环绕增强后********
正在执行 OrderDao 的 get() 方法……
正在执行 OrderDao 的 modify() 方法……

Spring JDK 动态代理 (了解)

JDK动态代理,针对目标对象的接口进行代理 ,动态生成接口的实现类 (必须有接口)

jdk动态代理是利用反射机制生成的一个实现代理接口InvocationHandler和目标类接口的匿名类,InvocationHandler其中有一个invoke方法,在调用真正的目标方法method.invoke(target,args)之前或者之后做代理操作。

过程要点

1.必须对接口生成代理

2.采用Proxy对象,通过newProxyInstance方法为目标创建代理对象。

该方法接收三个参数 :

(1)目标对象类加载器

(2)目标对象实现的接口

(3)代理后的处理程序InvocationHandler

3.实现InvocationHandler 接口中 invoke方法,在目标对象每个方法调用时,都会执行invoke

举一例子

//接口(表示代理的目标接口)
public interface ICustomerService {
    //保存
    void save();
    //查询
    int find();
}
//实现层
public class CustomerServiceImpl implements ICustomerService{
 
    @Override
    public void save() {
       System.out.println("客户保存了。。。。。");
    }
 
    @Override
    public int find() {
       System.out.println("客户查询数量了。。。。。");
       return 100;
    }
}
//专门用来生成jdk的动态代理对象的-通用
public class JdkProxyFactory{
    //target目标对象
    private Object target;
 
    //注入target目标对象
    public JdkProxyFactory(Object target) {
       this.target = target;
    }
 
    public Object getProxyObject(){
 
        /**
        * 参数1:目标对象的类加载器
        * 参数2:目标对象实现的接口
        * 参数3:回调方法对象
       */
       return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), 
            new InvocationHandler(){
                public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
                    //如果是保存的方法,执行记录日志操作
                    if(method.getName().equals("save")){
                        System.out.println("增强代码:写日志了。。。");
                     }
                    //目标对象原来的方法执行
                    Object object = method.invoke(target, args);
                    return object;
                 }
        });
    }
}
//目标:使用动态代理,对原来的方法进行功能增强,而无需更改原来的代码。
//JDK动态代理:基于接口的(对象的类型,必须实现接口!)
@Test
public void testJdkProxy(){
   //target(目标对象)
   ICustomerService target = new CustomerServiceImpl();
   //把目标对象放到放到代理工厂中
   JdkProxyFactory jdkProxyFactory = new JdkProxyFactory(target);
   //获取 Object代理对象:基于目标对象类型的接口的类型的子类型的对象
   //通过代理工厂的getProxyObject方法获取目标对象,并且必须使用接口对象去强转
   ICustomerService proxy = (ICustomerService)jdkProxyFactory.getProxyObject();
   //调用目标对象的方法
   proxy.save();
   System.out.println("————————————————————");
   proxy.find();
}

上面测试方法中的目标对象有两个,第一个就跟平常创建一个接口一样,这个时候如果调用上面的target.save();就不会有增强代码写日志了这一句,就只会调用接口自己的方法,而后面一个通过代理工厂获取的目标对象就是增强版的,在不改变目标代码的情况下增强其功能,这就是aop的体现

运行截图

同时要特别注意强转是转的接口不是接口实现类

错误
CustomerServiceImpl proxy = (CustomerServiceImpl)jdkProxyFactory.getProxyObject();
正确
ICustomerService proxy = (ICustomerService)jdkProxyFactory.getProxyObject();

Spring CGLlB 动态代理 (了解)

Cglib的引入为了解决类的直接代理问题(生成代理子类),不需要接口也可以代理

该代理方式需要相应的jar包,但不需要导入。因为Spring core包已经包含cglib ,而且同时包含了cglib 依赖的asm的包(动态字节码的操作类库)

举一例子

//没有接口的类
public class ProductService {
    public void save() {
       System.out.println("商品保存了。。。。。");
    }
 
    public int find() {
       System.out.println("商品查询数量了。。。。。");
       return 99;
    }
}
//cglib动态代理工厂:用来生成cglib代理对象
public class CglibProxyFactory implements MethodInterceptor{
    private Object target;
 
    //注入代理对象
    public CglibProxyFactory(Object target) {
       this.target = target;
    }
 
    //获取代理对象
    public Object getProxyObject(){
       //1.代理对象生成器(工厂思想)
       Enhancer enhancer = new Enhancer();
        // 类加载器
       enhancer.setClassLoader(target.getClass().getClassLoader());
 
       //2.在增强器上设置两个属性
       //设置要生成代理对象的目标对象:生成的目标对象类型的子类型
       enhancer.setSuperclass(target.getClass());
       //设置回调方法,这里的this是调用了下面的intercept方法
       enhancer.setCallback(this);
 
       //3.创建获取对象
       return enhancer.create();
    }
 
    //回调方法(代理对象的方法)
    /**
     *  参数1:代理对象
     *  参数2:目标对象的方法对象
     *  参数3:目标对象的方法的参数的值
     *  参数4:代理对象的方法对象
     */
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
       //如果是保存的方法,执行记录日志操作
       if(method.getName().equals("save")){
            System.out.println("增强代码:写日志了。。。");
       }
        //目标对象原来的方法执行
        //调用目标对象的某个方法,并且返回目标对象
       Object object = method.invoke(target, args);
       return object;
    }
}
//cglib动态代理:可以基于类(无需实现接口)生成代理对象
    @Test
    public void testCglibProxy(){
       //target目标:
       ProductService target = new ProductService();
 
       //代理工厂对象,注入目标
       CglibProxyFactory cglibProxyFactory = new CglibProxyFactory(target);
 
       //获取proxy
       //代理对象,其实是目标对象类型的子类型
       ProductService proxy = (ProductService)cglibProxyFactory.getProxyObject();
       //调用代理对象的方法
       proxy.save();
       System.out.println("—————————————————————");
       proxy.find();
    }

注意事项

  • 对接口创建代理优于对类创建代理,因为会产生更加松耦合的系统,所以spring默认是使用JDK代理。对类代理是让遗留系统或无法实现接口的第三方类库同样可以得到通知,这种方式应该是备用方案
  • 标记为final的方法不能够被通知。spring是为目标类产生子类。任何需要被通知的方法都被复写,将通知织入。final方法是不允许重写的
  • spring只支持方法连接点:不提供属性接入点,spring的观点是属性拦截破坏了封装。面向对象的概念是对象自己处理工作,其他对象只能通过方法调用的得到的结果

Spring 集成 AspectJ(熟悉)

Spring AOP 是一个简化版的 AOP 实现,并没有提供完整版的 AOP 功能。通常情况下,Spring AOP 是能够满足我们日常开发过程中的大多数场景的,但在某些情况下,我们可能需要使用 Spring AOP 范围外的某些 AOP 功能。

例如 Spring AOP 仅支持执行公共(public)非静态方法的调用作为连接点,如果我们需要向受保护的(protected)或私有的(private)的方法进行增强,此时就需要使用功能更加全面的 AOP 框架来实现,其中使用最多的就是 AspectJ。

AspectJ 是一个基于 Java 语言的全功能的 AOP 框架,它并不是 Spring 组成部分,是一款独立的 AOP 框架。

但由于 AspectJ 支持通过 Spring 配置 AspectJ 切面,因此它是 Spring AOP 的完美补充,通常情况下,我们都是将 AspectJ 和 Spirng 框架一起使用,简化 AOP 操作。

使用 AspectJ 需要在 Spring 项目中导入 Spring AOP 和 AspectJ 相关 Jar 包。

  • spring-aop-xxx.jar
  • spring-aspects-xxx.jar
  • aspectjweaver-xxxx.jar

在以上 3 个 Jar 包中,spring-aop-xxx.jar 和 spring-aspects-xxx.jar 为 Spring 框架提供的 Jar 包,而 aspectjweaver-xxxx.jar 则是 AspectJ 提供的。

AspectJ 基于 XML 开发AOP(熟悉)

在 Spring 项目中通过 XML 配置,对切面(Aspect 或 Advisor)、切点(PointCut)以及通知(Advice)进行定义和管理,以实现基于 AspectJ 的 AOP 开发。

Spring 提供了基于 XML 的 AOP 支持,并提供了一个名为“aop”的命名空间,该命名空间提供了一个 <aop:config> 元素。

  • 在 Spring 配置中,所有的切面信息(切面、切点、通知)都必须定义在 <aop:config> 元素中;
  • 在 Spring 配置中,可以使用多个 <aop:config>。
  • 每一个 <aop:config> 元素内可以包含 3 个子元素: pointcut、advisor 和 aspect ,这些子元素必须按照这个顺序进行声明。

引入 aop 命名空间

首先,我们需要在 XML 配置文件中导入 Spring aop 命名空间的约束,如下所示。

下面我们通过一个示例来演示下 Spring 集成 AspectJ 基于 XML 实现 AOP 开发。

1. 新建一个名为 my-spring-asepctj-demo 的 Java 项目,并将以下依赖 Jar 包导入到该项目中。

  • commons-logging-1.2.jar
  • spring-aop-5.3.13.jar
  • spring-aspects-5.3.13.jar
  • spring-beans-5.3.13.jar
  • spring-context-5.3.13.jar
  • spring-core-5.3.13.jar
  • spring-expression-5.3.13.jar
  • aspectjweaver-1.9.7.jar

2. 在 net.biancheng.c.dao 包下,创建一个名为 OrderDao 的接口,代码如下。

3. 在 net.biancheng.c.dao.impl 包下,创建 OrderDao 的实现类 OrderDaoImpl,代码如下。

package net.biancheng.c.dao.impl;
 
import net.biancheng.c.dao.OrderDao;
 
public class OrderDaoImpl implements OrderDao {
    @Override
    public void add() {
        System.out.println("正在执行 OrderDao 中的 add() 方法");
    }
 
    @Override
    public void delete() {
        System.out.println("正在执行 OrderDao 中的 delete() 方法");
    }
 
    @Override
    public int modify() {
        System.out.println("正在执行 OrderDao 中的 modify() 方法");
        return 1;
    }
 
    @Override
    public void get() {
        //异常
        int a = 10 / 0;
        System.out.println("正在执行 OrderDao 中的 get() 方法");
    }
}

 4. 在 net.biancheng.c 包下,创建一个名为 MyOrderAspect 的类,代码如下。

package net.biancheng.c;
 
import org.aspectj.lang.ProceedingJoinPoint;
 
public class MyOrderAspect {
    
    public void before() {
        System.out.println("前置增强……");
    }
 
    public void after() {
        System.out.println("最终增强……");
    }
 
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕增强---前……");
        proceedingJoinPoint.proceed();
        System.out.println("环绕增强---后……");
    }
 
    public void afterThrow(Throwable exception) {
        System.out.println("异常增强…… 异常信息为:" + exception.getMessage());
    }
 
    public void afterReturning(Object returnValue) {
        System.out.println("后置返回增强…… 方法返回值为:" + returnValue);
    }
}

 5. 在 src 目录下创建一个 Spring 配置文件 Beans2.xml,配置内容如下。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--定义 Bean-->
    <bean id="orderDao" class="net.biancheng.c.dao.impl.OrderDaoImpl"></bean>
    <!--定义切面-->
    <bean id="myOrderAspect" class="net.biancheng.c.MyOrderAspect"></bean>
 
    <aop:config>
        <aop:pointcut id="beforePointCut" expression="execution(* net.biancheng.c.dao.OrderDao.add(..))"/>
 
        <aop:pointcut id="throwPointCut" expression="execution(* net.biancheng.c.dao.OrderDao.get(..))"/>
 
        <aop:pointcut id="afterReturnPointCut" expression="execution(* net.biancheng.c.dao.OrderDao.modify(..))"/>
 
        <aop:pointcut id="afterPointCut" expression="execution(* net.biancheng.c.dao.OrderDao.*(..))"/>
 
        <aop:aspect ref="myOrderAspect">
            <!--前置增强-->
            <aop:before method="before" pointcut-ref="beforePointCut"></aop:before>
            <!--后置返回增强-->
            <aop:after-returning method="afterReturning" pointcut-ref="afterReturnPointCut"
                                 returning="returnValue"></aop:after-returning>
            <!--异常通知-->
            <aop:after-throwing method="afterThrow" pointcut-ref="throwPointCut"
                                throwing="exception"></aop:after-throwing>
            <!--最终通知-->
            <aop:after method="after" pointcut-ref="afterPointCut"></aop:after>
            <!--环绕通知-->
            <aop:around method="around" pointcut-ref="beforePointCut"></aop:around>
        </aop:aspect>
    </aop:config>
 
</beans>

AspectJ 基于注解开发 AOP (熟悉)

在 Spring 中,虽然我们可以使用 XML 配置文件可以实现 AOP 开发,但如果所有的配置都集中在 XML 配置文件中,就势必会造成 XML 配置文件过于臃肿,从而给维护和升级带来一定困难。为此,AspectJ 框架为 AOP 开发提供了一套 @AspectJ 注解。它允许我们直接在 Java 类中通过注解的方式对切面(Aspect)、切入点(Pointcut)和增强(Advice)进行定义,Spring 框架可以根据这些注解生成 AOP 代理。

关于注解的介绍如表 1 所示。

表 1 Annotation 注解介绍
名称说明
@Aspect用于定义一个切面。
@Pointcut用于定义一个切入点。
@Before用于定义前置通知,相当于 BeforeAdvice。
@AfterReturning用于定义后置通知,相当于 AfterReturningAdvice。
@Around用于定义环绕通知,相当于 MethodInterceptor。
@AfterThrowing用于定义抛出通知,相当于 ThrowAdvice。
@After用于定义最终通知,不管是否异常,该通知都会执行。
@DeclareParents用于定义引介通知,相当于 IntroductionInterceptor(不要求掌握)。

启用 @AspectJ 注解支持

在使用 @AspectJ 注解进行 AOP 开发前,首先我们要先启用 @AspectJ 注解支持。

我们可以通过以下 2 种方式来启用 @AspectJ 注解。

1)使用 Java 配置类启用

我们可以在 Java 配置类(标注了 @Configuration 注解的类)中,使用 @EnableAspectJAutoProxy 和 @ComponentScan 注解启用 @AspectJ 注解支持。

定义通知 

AspectJ 为我们提供了以下 6 个注解,来定义 6 种不同类型的通知(Advice),如下表。

注解说明
@Before用于定义前置通知,相当于 BeforeAdvice。
@AfterReturning用于定义后置通知,相当于 AfterReturningAdvice。
@Around用于定义环绕通知,相当于 MethodInterceptor。
@AfterThrowing用于定义抛出通知,相当于 ThrowAdvice。
@After用于定义最终通知,不管是否异常,该通知都会执行。
@DeclareParents用于定义引介通知,相当于 IntroductionInterceptor(不要求掌握)。

以上这些通知注解中都有一个 value 属性,这个 value 属性的取值就是这些通知(Advice)作用的切点(PointCut),它既可以是切入点表达式,也可以是切入点的引用(切入点对应的方法名称),示例代码如下。

示例

下面,我们就通过一个完整的实例,来演示下如何通过注解的方式实现 AspectJ AOP 开发。

1. 新建一个名为 my-spring-asepctj-demo2 的 Java 项目,并将以下依赖 Jar 包导入到该项目中。

  • commons-logging-1.2.jar
  • spring-aop-5.3.13.jar
  • spring-aspects-5.3.13.jar
  • spring-beans-5.3.13.jar
  • spring-context-5.3.13.jar
  • spring-core-5.3.13.jar
  • spring-expression-5.3.13.jar
  • aspectjweaver-1.9.7.jar

2. 在 net.biancheng.c.dao 包下,创建一个名为 UserDao 的接口,代码如下。

5. 在 net.biancheng.c 包下,创建一个名为 MyAspect 的切面类,代码如下。

package net.biancheng.c;
 
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
 
@Component // 定义成 Bean
@Aspect //定义为切面
public class MyAspect {
    @Before("execution(* net.biancheng.c.dao.UserDao.add(..))")
    public void before(JoinPoint joinPoint) {
        System.out.println("前置增强……" + joinPoint);
    }
 
    @After("execution(* net.biancheng.c.dao.UserDao.get(..))")
    public void after(JoinPoint joinPoint) {
        System.out.println("最终增强……" + joinPoint);
    }
 
 
    /**
     * 将 net.biancheng.c.dao包下的 UserDao 类中的 get() 方法 定义为一个切点
     */
    @Pointcut(value = "execution(* net.biancheng.c.dao.UserDao.get(..))")
    public void pointCut1() {
 
    }
 
    /**
     * 将 net.biancheng.c.dao包下的 UserDao 类中的 delete() 方法 定义为一个切点
     */
    @Pointcut(value = "execution(* net.biancheng.c.dao.UserDao.delete(..))")
    public void pointCut2() {
 
    }
 
    //使用切入点引用
    @Around("MyAspect.pointCut2()")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕增强……1");
        proceedingJoinPoint.proceed();
        System.out.println("环绕增强……2");
    }
 
    //使用切入点表达式
    @AfterReturning(value = "execution(* net.biancheng.c.dao.UserDao.modify(..))", returning = "returnValue")
    public void afterReturning(Object returnValue) {
        System.out.println("后置返回增强……,方法返回值为:" + returnValue);
    }
}

Spring JdbcTemplate 类 (熟悉)

JDBC 是 Java 提供的一种用于执行 SQL 语句的 API,可以对多种关系型数据库(例如 MySQL、Oracle 等)进行访问。

但在实际的企业级应用开发中,却很少有人直接使用原生的 JDBC API 进行开发,这是因为使用 JDBC API 对数据库进行操作十分繁琐,需要我们对每一步都做到“步步把控,处处关心”,例如我们需要手动控制数据库连接的开启,异常处理、事务处理、最后还要手动关闭连接释放资源等等。

Spring 提供了一个 Spring JDBC 模块,它对 JDBC API 进行了封装,其的主要目的降低 JDBC API 的使用难度,以一种更直接、更简洁的方式使用 JDBC API。

使用 Spring JDBC,开发人员只需要定义必要的参数、指定需要执行的 SQL 语句,即可轻松的进行 JDBC 编程,对数据库进行访问。

至于驱动的加载、数据库连接的开启与关闭、SQL 语句的创建与执行、异常处理以及事务处理等繁杂乏味的工作,则都是由 Spring JDBC 完成的。这样就可以使开发人员从繁琐的 JDBC API 中解脱出来,有更多的精力专注于业务的开发。

Spring JDBC 提供了多个实用的数据库访问工具,以简化 JDBC 的开发,其中使用最多就是 JdbcTemplate。

JdbcTemplate

JdbcTemplate 是 Spring JDBC 核心包(core)中的核心类,它可以通过配置文件、注解、Java 配置类等形式获取数据库的相关信息,实现了对 JDBC 开发过程中的驱动加载、连接的开启和关闭、SQL 语句的创建与执行、异常处理、事务处理、数据类型转换等操作的封装。我们只要对其传入SQL 语句和必要的参数即可轻松进行 JDBC 编程。

JdbcTemplate 的全限定命名为 org.springframework.jdbc.core.JdbcTemplate,它提供了大量的查询和更新数据库的方法,如下表所示。

方法说明
public int update(String sql)用于执行新增、更新、删除等语句;
  • sql:需要执行的 SQL 语句;
  • args 表示需要传入到 SQL 语句中的参数。
public int update(String sql,Object... args)
public void execute(String sql)可以执行任意 SQL,一般用于执行 DDL 语句;
  • sql:需要执行的 SQL 语句;
  • action 表示执行完 SQL 语句后,要调用的函数。
public T execute(String sql, PreparedStatementCallback action)
public <T> List<T> query(String sql, RowMapper<T> rowMapper, @Nullable Object... args) 
用于执行查询语句;
  • sql:需要执行的 SQL 语句;
  • rowMapper:用于确定返回的集合(List)的类型;
  • args:表示需要传入到 SQL 语句的参数。
public <T> T queryForObject(String sql, RowMapper<T> rowMapper, @Nullable Object... args)
public int[] batchUpdate(String sql, List<Object[]> batchArgs, final int[] argTypes) 用于批量执行新增、更新、删除等语句;
  • sql:需要执行的 SQL 语句;
  • argTypes:需要注入的 SQL 参数的 JDBC 类型;
  • batchArgs:表示需要传入到 SQL 语句的参数。

代码演示:Spring JdbcTemplate(使用详解) 

Spring 集成 Log4J(熟悉)

简介

Log4j有三个主要的组件:Loggers(记录器),Appenders (输出源)和Layouts(布局)。这里可简单理解为日志类别,日志要输出的地方和日志以何种形式输出。综合使用这三个组件可以轻松地记录信息的类型和级别,并可以在运行时控制日志输出的样式和位置。

1. Loggers

Loggers组件在此系统中被分为五个级别:DEBUGINFOWARNERRORFATAL。这五个级别是有顺序的,DEBUG < INFO < WARN < ERROR < FATAL,分别用来指定这条日志信息的重要程度,明白这一点很重要,Log4j有一个规则:只输出级别不低于设定级别的日志信息,假设Loggers级别设定为INFO,则INFOWARNERRORFATAL级别的日志信息都会输出,而级别比INFO低的DEBUG则不会输出。

2. Appenders

禁用和使用日志请求只是Log4j的基本功能,Log4j日志系统还提供许多强大的功能,比如允许把日志输出到不同的地方,如控制台(Console)、文件(Files)等,可以根据天数或者文件大小产生新的文件,可以以流的形式发送到其它地方等等。

常使用的类如下:

org.apache.log4j.ConsoleAppender(控制台)
org.apache.log4j.FileAppender(文件)
org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件)
org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)
org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)

3. Layouts

有时用户希望根据自己的喜好格式化自己的日志输出,Log4j可以在Appenders的后面附加Layouts来完成这个功能。Layouts提供四种日志输出样式,如根据HTML样式、自由指定样式、包含日志级别与信息的样式和包含日志时间、线程、类别等信息的样式。

 常使用的类如下:

org.apache.log4j.HTMLLayout(以HTML表格形式布局)
org.apache.log4j.PatternLayout(可以灵活地指定布局模式)
org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串)
org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等信息)

配置详解 

Log4j配置文件实现了输出到控制台、文件、回滚文件、发送日志邮件、输出到数据库日志表、自定义标签等全套功能。
log4j.rootLogger=DEBUG,console,dailyFile,im
log4j.additivity.org.apache=true
# 控制台(console)
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.ImmediateFlush=true
log4j.appender.console.Target=System.err
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%-5p] %d(%r) --> [%t] %l: %m %x %n

# 日志文件(logFile)
log4j.appender.logFile=org.apache.log4j.FileAppender
log4j.appender.logFile.Threshold=DEBUG
log4j.appender.logFile.ImmediateFlush=true
log4j.appender.logFile.Append=true
log4j.appender.logFile.File=D:/logs/log.log4j
log4j.appender.logFile.layout=org.apache.log4j.PatternLayout
log4j.appender.logFile.layout.ConversionPattern=[%-5p] %d(%r) --> [%t] %l: %m %x %n
# 回滚文件(rollingFile)
log4j.appender.rollingFile=org.apache.log4j.RollingFileAppender
log4j.appender.rollingFile.Threshold=DEBUG
log4j.appender.rollingFile.ImmediateFlush=true
log4j.appender.rollingFile.Append=true
log4j.appender.rollingFile.File=D:/logs/log.log4j
log4j.appender.rollingFile.MaxFileSize=200KB
log4j.appender.rollingFile.MaxBackupIndex=50
log4j.appender.rollingFile.layout=org.apache.log4j.PatternLayout
log4j.appender.rollingFile.layout.ConversionPattern=[%-5p] %d(%r) --> [%t] %l: %m %x %n
# 定期回滚日志文件(dailyFile)
log4j.appender.dailyFile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.dailyFile.Threshold=DEBUG
log4j.appender.dailyFile.ImmediateFlush=true
log4j.appender.dailyFile.Append=true
log4j.appender.dailyFile.File=D:/logs/log.log4j
log4j.appender.dailyFile.DatePattern='.'yyyy-MM-dd
log4j.appender.dailyFile.layout=org.apache.log4j.PatternLayout
log4j.appender.dailyFile.layout.ConversionPattern=[%-5p] %d(%r) --> [%t] %l: %m %x %n
# 应用于socket
log4j.appender.socket=org.apache.log4j.RollingFileAppender
log4j.appender.socket.RemoteHost=localhost
log4j.appender.socket.Port=5001
log4j.appender.socket.LocationInfo=true
# Set up for Log Factor 5
log4j.appender.socket.layout=org.apache.log4j.PatternLayout
log4j.appender.socket.layout.ConversionPattern=[%-5p] %d(%r) --> [%t] %l: %m %x %n
# Log Factor 5 Appender
log4j.appender.LF5_APPENDER=org.apache.log4j.lf5.LF5Appender
log4j.appender.LF5_APPENDER.MaxNumberOfRecords=2000
# 发送日志到指定邮件
log4j.appender.mail=org.apache.log4j.net.SMTPAppender
log4j.appender.mail.Threshold=FATAL
log4j.appender.mail.BufferSize=10
log4j.appender.mail.From = xxx@mail.com
log4j.appender.mail.SMTPHost=mail.com
log4j.appender.mail.Subject=Log4J Message
log4j.appender.mail.To= xxx@mail.com
log4j.appender.mail.layout=org.apache.log4j.PatternLayout
log4j.appender.mail.layout.ConversionPattern=[%-5p] %d(%r) --> [%t] %l: %m %x %n
# 应用于数据库
log4j.appender.database=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.database.URL=jdbc:mysql://localhost:3306/test
log4j.appender.database.driver=com.mysql.jdbc.Driver
log4j.appender.database.user=root
log4j.appender.database.password=
log4j.appender.database.sql=INSERT INTO LOG4J (Message) VALUES('=[%-5p] %d(%r) --> [%t] %l: %m %x %n')
log4j.appender.database.layout=org.apache.log4j.PatternLayout
log4j.appender.database.layout.ConversionPattern=[%-5p] %d(%r) --> [%t] %l: %m %x %n

# 自定义Appender
log4j.appender.im = net.cybercorlin.util.logger.appender.IMAppender
log4j.appender.im.host = mail.cybercorlin.net
log4j.appender.im.username = username
log4j.appender.im.password = password
log4j.appender.im.recipient = corlin@cybercorlin.net
log4j.appender.im.layout=org.apache.log4j.PatternLayout
log4j.appender.im.layout.ConversionPattern=[%-5p] %d(%r) --> [%t] %l: %m %x %n

Log4J的配置文件(Configuration File)就是用来设置记录器的级别、存放器和布局的,它可接key=value格式的设置或xml格式的设置信息。通过配置,可以创建出Log4J的运行环境。

在实际应用中,要使Log4j在系统中运行须事先设定配置文件。

配置文件事实上也就是对Logger、Appender及Layout进行相应设定。

Log4j支持两种配置文件格式,一种是XML格式的文件,一种是properties属性文件。下面以properties属性文件为例介绍log4j.properties的配置。

1.配置根Logger

#(默认输出目的地,当前传入类名)
log4j.rootLogger = [ level ] , appenderName1, appenderName2, … 
# 表示Logger不会在父Logger的appender里输出,默认为true。
log4j.additivity.org.apache=false 

level :设定日志记录的最低级别,可设的值有OFFFATALERRORWARNINFODEBUGALL或者自定义的级别,Log4j建议只使用中间四个级别。通过在这里设定级别,您可以控制应用程序中相应级别的日志信息的开关,比如在这里设定了INFO级别,则应用程序中所有DEBUG级别的日志信息将不会被打印出来。

appenderName:就是指定日志信息要输出到哪里。可以同时指定多个输出目的地,用逗号隔开。

例如:log4j.rootLogger=INFO,A1,B2,C3

 2.配置日志信息输出目的地(appender)

log4j.appender.appenderName = className

appenderName:自定义appderName,在log4j.rootLogger设置中使用;

className:可设值如下:

  • org.apache.log4j.ConsoleAppender(控制台)
  • org.apache.log4j.FileAppender(文件)
  • org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件)
  • org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)
  • org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)

1) ConsoleAppender选项:

Threshold=WARN:指定日志信息的最低输出级别,默认为DEBUG。
ImmediateFlush=true:表示所有消息都会被立即输出,设为false则不输出,默认值是true。
Target=System.err:默认值是System.out

2) FileAppender选项:

Threshold=WARN:指定日志信息的最低输出级别,默认为DEBUG。
ImmediateFlush=true:表示所有消息都会被立即输出,设为false则不输出,默认值是true。
Append=false:true表示消息增加到指定文件中,false则将消息覆盖指定的文件内容,默认值是true。
File=D:/logs/logging.log4j:指定消息输出到logging.log4j文件中。

3) DailyRollingFileAppender选项:

Threshold=WARN:指定日志信息的最低输出级别,默认为DEBUG。
ImmediateFlush=true:表示所有消息都会被立即输出,设为false则不输出,默认值是true。
Append=false:true表示消息增加到指定文件中,false则将消息覆盖指定的文件内容,默认值是true。
File=D:/logs/logging.log4j:指定当前消息输出到logging.log4j文件中。
DatePattern='.'yyyy-MM:每月滚动一次日志文件,即每月产生一个新的日志文件。当前月的日志文件名为logging.log4j,前一个月的日志文件名为logging.log4j.yyyy-MM。
另外,也可以指定按周、天、时、分等来滚动日志文件,对应的格式如下:
1)'.'yyyy-MM:每月
2)'.'yyyy-ww:每周
3)'.'yyyy-MM-dd:每天
4)'.'yyyy-MM-dd-a:每天两次
5)'.'yyyy-MM-dd-HH:每小时
6)'.'yyyy-MM-dd-HH-mm:每分钟

4) 配置日志信息的输出格式(Layout):

log4j.appender.appenderName.layout=className
className:可设值如下:
1.org.apache.log4j.HTMLLayout(以HTML表格形式布局)
2.org.apache.log4j.PatternLayout(可以灵活地指定布局模式)
3.org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串)
4.org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息)
HTMLLayout选项:
LocationInfo=true:输出java文件名称和行号,默认值是false。
Title=My Logging: 默认值是Log4J Log Messages。
(2)PatternLayout选项:
ConversionPattern=%m%n:设定以怎样的格式显示消息。

格式化符号说明:

%p:输出日志信息的优先级,即DEBUG,INFO,WARN,ERROR,FATAL。
%d:输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,如:%d{yyyy/MM/dd HH:mm:ss,SSS}。
%r:输出自应用程序启动到输出该log信息耗费的毫秒数。
%t:输出产生该日志事件的线程名。
%l:输出日志事件的发生位置,相当于%c.%M(%F:%L)的组合,包括类全名、方法、文件名以及在代码中的行数。例如:test.TestLog4j.main(TestLog4j.java:10)。
%c:输出日志信息所属的类目,通常就是所在类的全名。
%M:输出产生日志信息的方法名。
%F:输出日志消息产生时所在的文件名称。
%L::输出代码中的行号。
%m::输出代码中指定的具体日志信息。
%n:输出一个回车换行符,Windows平台为"rn",Unix平台为"n"。
%x:输出和当前线程相关联的NDC(嵌套诊断环境),尤其用到像java servlets这样的多客户多线程的应用中。
%%:输出一个"%"字符。
另外,还可以在%与格式字符之间加上修饰符来控制其最小长度、最大长度、和文本的对齐方式。如:
\1) c:指定输出category的名称,最小的长度是20,如果category的名称长度小于20的话,默认的情况下右对齐。
2)%-20c:"-"号表示左对齐。
3)%.30c:指定输出category的名称,最大的长度是30,如果category的名称长度大于30的话,就会将左边多出的字符截掉,但小于30的话也不会补空格。

Spring 事务(熟悉)

事务(Transaction)是基于关系型数据库(RDBMS)的企业应用的重要组成部分。在软件开发领域,事务扮演者十分重要的角色,用来确保应用程序数据的完整性和一致性。

事务具有 4 个特性:原子性、一致性、隔离性和持久性,简称为 ACID 特性。

  • 原子性(Atomicity):一个事务是一个不可分割的工作单位,事务中包括的动作要么都做要么都不做。
  • 一致性(Consistency):事务必须保证数据库从一个一致性状态变到另一个一致性状态,一致性和原子性是密切相关的。
  • 隔离性(Isolation):一个事务的执行不能被其它事务干扰,即一个事务内部的操作及使用的数据对并发的其它事务是隔离的,并发执行的各个事务之间不能互相打扰。
  • 持久性(Durability):持久性也称为永久性,指一个事务一旦提交,它对数据库中数据的改变就是永久性的,后面的其它操作和故障都不应该对其有任何影响。

事务允许我们将几个或一组操作组合成一个要么全部成功、要么全部失败的工作单元。如果事务中的所有的操作都执行成功,那自然万事大吉。但如果事务中的任何一个操作失败,那么事务中所有的操作都会被回滚,已经执行成功操作也会被完全清除干净,就好像什么事都没有发生一样。

在现实世界中,最常见的与事务相关的例子可能就是银行转账了。假设我们需要将 1000 元从 A 账户中转到 B 账户中,这个转账操作共涉及了以下两个操作。

  • 从 A 账户中扣除 1000 元;
  • 往 B 账户中存入 1000 元。

如果 A 账户成功地扣除了 1000 元,但向 B 账户存入时失败的话,那么我们将凭空损失 1000 元;如果 A 账户扣款时失败,但却成功地向 B 账户存入 1000 元的话,我们的账户就凭空多出了 1000 元,那么银行就会遭受损失。因此我们必须保证事务中的所有操作要么全部成功,要么全部失败,理解了这一点,我们也就抓住了事务的核心。

作为一款优秀的开源框架和应用平台,Spring 也对事务提供了很好的支持。Spring 借助 IoC 容器强大的配置能力,为事务提供了丰富的功能支持。

事务管理方式

Spring 支持以下 2 种事务管理方式。

事务管理方式说明
编程式事务管理编程式事务管理是通过编写代码实现的事务管理。

这种方式能够在代码中精确地定义事务的边界,我们可以根据需求规定事务从哪里开始,到哪里结束。
声明式事务管理Spring 声明式事务管理在底层采用了 AOP 技术,其最大的优点在于无须通过编程的方式管理事务,只需要在配置文件中进行相关的规则声明,就可以将事务规则应用到业务逻辑中。

选择编程式事务还是声明式事务,很大程度上就是在控制权细粒度和易用性之间进行权衡。

  • 编程式对事物控制的细粒度更高,我们能够精确的控制事务的边界,事务的开始和结束完全取决于我们的需求,但这种方式存在一个致命的缺点,那就是事务规则与业务代码耦合度高,难以维护,因此我们很少使用这种方式对事务进行管理。
  • 声明式事务易用性更高,对业务代码没有侵入性,耦合度低,易于维护,因此这种方式也是我们最常用的事务管理方式。

Spring 的声明式事务管理主要通过以下 2 种方式实现:

  • 基于 XML 方式的声明式事务管理
  • 基于注解方式的声明式事务管理

事务管理器

Spring 并不会直接管理事务,而是通过事务管理器对事务进行管理的。

在 Spring 中提供了一个 org.springframework.transaction.PlatformTransactionManager 接口,这个接口被称为 Spring 的事务管理器,其源码如下。

public interface PlatformTransactionManager extends TransactionManager {
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;

    void rollback(TransactionStatus status) throws TransactionException;
}

该接口中各方法说明如下: 

名称说明
TransactionStatus getTransaction(TransactionDefinition definition)用于获取事务的状态信息
void commit(TransactionStatus status)用于提交事务
 void rollback(TransactionStatus status)用于回滚事务

Spring 为不同的持久化框架或平台(例如 JDBC、Hibernate、JPA 以及 JTA 等)提供了不同的 PlatformTransactionManager 接口实现,这些实现类被称为事务管理器实现。

实现类说明
org.springframework.jdbc.datasource.DataSourceTransactionManager使用 Spring JDBC 或 iBatis 进行持久化数据时使用。
org.springframework.orm.hibernate3.HibernateTransactionManager使用 Hibernate 3.0 及以上版本进行持久化数据时使用。
org.springframework.orm.jpa.JpaTransactionManager使用 JPA 进行持久化时使用。
org.springframework.jdo.JdoTransactionManager当持久化机制是 Jdo 时使用。
org.springframework.transaction.jta.JtaTransactionManager使用 JTA 来实现事务管理,在一个事务跨越多个不同的资源(即分布式事务)使用该实现。

这些事务管理器的使用方式十分简单,我们只要根据持久化框架(或平台)选用相应的事务管理器实现,即可实现对事物的管理,而不必关心实际事务实现到底是什么。

TransactionDefinition 接口

Spring 将 XML 配置中的事务信息封装到对象 TransactionDefinition 中,然后通过事务管理器的 getTransaction() 方法获得事务的状态(TransactionStatus),并对事务进行下一步的操作。

TransactionDefinition 接口提供了获取事务相关信息的方法,接口定义如下。

public interface TransactionDefinition {
    int getPropagationBehavior();
    int getIsolationLevel();
    String getName();
    int getTimeout();
    boolean isReadOnly();
}

该接口中方法说明如下。

方法说明
String getName()获取事务的名称
int getIsolationLevel()获取事务的隔离级别
int getPropagationBehavior()获取事务的传播行为
int getTimeout()获取事务的超时时间
boolean isReadOnly()获取事务是否只读

事务的隔离级别

事务的隔离级别定义了一个事务可能受其他并发事务影响的程度。

在实际应用中,经常会出现多个事务同时对同一数据执行不同操作,来实现各自的任务的情况。此时就有可能导致脏读、幻读以及不可重复读等问题的出现。

在理想情况下,事务之间是完全隔离的,这自然不会出现上述问题。但完全的事务隔离会导致性能问题,而且并不是所有的应用都需要事务的完全隔离,因此有时应用程序在事务隔离上也有一定的灵活性。

Spring 中提供了以下隔离级别,我们可以根据自身的需求自行选择合适的隔离级别。

方法说明
ISOLATION_DEFAULT使用后端数据库默认的隔离级别
ISOLATION_READ_UNCOMMITTED允许读取尚未提交的更改,可能导致脏读、幻读和不可重复读
ISOLATION_READ_COMMITTEDOracle 默认级别,允许读取已提交的并发事务,防止脏读,可能出现幻读和不可重复读
ISOLATION_REPEATABLE_READMySQL 默认级别,多次读取相同字段的结果是一致的,防止脏读和不可重复读,可能出现幻读
ISOLATION_SERIALIZABLE完全服从 ACID 的隔离级别,防止脏读、不可重复读和幻读

事务的传播行为

事务传播行为(propagation behavior)指的是,当一个事务方法被另一个事务方法调用时,这个事务方法应该如何运行。例如,事务方法 A 在调用事务方法 B 时,B 方法是继续在调用者 A 方法的事务中运行呢,还是为自己开启一个新事务运行,这就是由事务方法 B 的事务传播行为决定的。

事务方法指的是能让数据库表数据发生改变的方法,例如新增数据、删除数据、修改数据的方法。

Spring 提供了以下 7 种不同的事务传播行为。

名称说明
PROPAGATION_MANDATORY支持当前事务,如果不存在当前事务,则引发异常。
PROPAGATION_NESTED如果当前事务存在,则在嵌套事务中执行。
PROPAGATION_NEVER不支持当前事务,如果当前事务存在,则引发异常。
PROPAGATION_NOT_SUPPORTED不支持当前事务,始终以非事务方式执行。
PROPAGATION_REQUIRED默认传播行为,如果存在当前事务,则当前方法就在当前事务中运行,如果不存在,则创建一个新的事务,并在这个新建的事务中运行。
PROPAGATION_REQUIRES_NEW创建新事务,如果已经存在事务则暂停当前事务。
PROPAGATION_SUPPORTS支持当前事务,如果不存在事务,则以非事务方式执行。

TransactionStatus 接口

TransactionStatus 接口提供了一些简单的方法,来控制事务的执行、查询事务的状态,接口定义如下。

public interface TransactionStatus extends SavepointManager {
    boolean isNewTransaction();
    boolean hasSavepoint();
    void setRollbackOnly();
    boolean isRollbackOnly();
    boolean isCompleted();
}

该接口中各方法说明如下。 

名称说明
boolean hasSavepoint()获取是否存在保存点
boolean isCompleted()获取事务是否完成
boolean isNewTransaction()获取是否是新事务
boolean isRollbackOnly()获取事务是否回滚
void setRollbackOnly()设置事务回滚
 

Spring 编程式事务管理(熟悉)

spring框架提供了两种编程式事务管理方式:

  • 使用TransactionTemplate
  • 直接使用PlatformTransactionManager实现

1、TransactionTemplate

TransactionTemplate采用与其他spring模板相同的方法,它使用一种回调方法,使应用程序代码可以处理获取和释放事务资源,让开发人员更加专注于业务逻辑。

public class SimpleService implements Service {
  private final TransactionTemplate transactionTemplate;
  public SimpleService(PlatformTransactionManager manager) {
    Assert.notNull(manager,"the 'manager' argument must not be null");
    this.transactionTemplate = new TransactionTemplate(manager);
  }

  public Object method1() {
    return transactionTemplate.execute(new TransactionCallback() {
      public Object doInTransaction(TransactionStatus status) {
        updateOper1();
        return updateOper2();
      }
    });
  }
}

<bean id="sharedTransactionTemplate" 
  class="org.springframework.transaction.support.TransactionTemplate">
  <property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/>
  <property name="timeout" value="30"/>
</bean>

2、PlatformTransactionManager

也可以直接使用org.springframework.transaction.PlatformTransactionManager来管理事务,只需通过bean引用将正在使用的PlatformTransactionManager的实现传递给bean。然后使用TransactionDefinition和TransactionStatus对象来启动、回滚和提交事务。

DefaultTransactionDefinition dtf = new DefaultTransactionDefinition();
dtf.setName("txName");
dtf.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(dtf);
try {
  //...
} catch(MyException ex) {
  txManager.rollback(status);
  throw ex;
}
txManager.commit(status);

 如果应用中只有很少量的事务操作,编程式事务管理通常是很好的选择。如果应用程序有大量的事务操作,则声明式事务管理通常是最优选择。它使事务不受业务逻辑的影响,并且在配置上也很简单。

好文参考:Spring编程式事务详解_51CTO博客_spring编程式事务管理

Spring 基于 XML 实现事务管理 (熟悉)

Spring 声明式事务管理是通过 AOP 实现的,其本质是对方法前后进行拦截,然后在目标方法开始之前创建(或加入)一个事务,在执行完目标方法后,根据执行情况提交或者回滚事务。

声明式事务最大的优点就是对业务代码的侵入性低,可以将业务代码和事务管理代码很好地进行解耦。

Spring 实现声明式事务管理主要有 2 种方式:

  • 基于 XML 方式的声明式事务管理。
  • 通过 Annotation 注解方式的事务管理。

下面介绍如何通过 XML 的方式实现声明式事务管理,步骤如下。

1. 引入 tx 命名空间

Spring 提供了一个 tx 命名空间,借助它可以极大地简化 Spring 中的声明式事务的配置。

想要使用 tx 命名空间,第一步就是要在 XML 配置文件中添加 tx 命名空间的约束。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">

注意:由于 Spring 提供的声明式事务管理是依赖于 Spring AOP 实现的,因此我们在 XML 配置文件中还应该添加与 aop 命名空间相关的配置。 

2. 配置事务管理器

接下来,我们就需要借助数据源配置,定义相应的事务管理器实现(PlatformTransactionManager 接口的实现类)的 Bean,配置内容如下。 

<!--配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <!--数据库连接地址-->
    <property name="url" value="xxx"/>
    <!--数据库的用户名-->
    <property name="username" value="xxx"/>
    <!--数据库的密码-->
    <property name="password" value="xxx"/>
    <!--数据库驱动-->
    <property name="driverClassName" value="xxx"/>
</bean>

<!--配置事务管理器,以 JDBC 为例-->
<bean id="transactionManager"
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

在以上配置中,配置的事务管理器实现为 DataSourceTransactionManager,即为 JDBC 和 iBatis 提供的 PlatformTransactionManager 接口实现。

3. 配置事务通知

在 Spring 的 XML 配置文件中配置事务通知,指定事务作用的方法以及所需的事务属性。 

<!--配置通知-->
<tx:advice id="tx-advice" transaction-manager="transactionManager">
    <!--配置事务参数-->
    <tx:attributes>
        <tx:method name="create*" propagation="REQUIRED" isolation="DEFAULT" read-only="false" timeout="10"/>
    </tx:attributes>
</tx:advice>

事务管理器配置

当我们使用 <tx:advice> 来声明事务时,需要通过 transaction-manager 参数来定义一个事务管理器,这个参数的取值默认为 transactionManager。

如果我们自己设置的事务管理器(第 2 步中设置的事务管理器 id)恰好与默认值相同,则可以省略对改参数的配置。

<tx:advice id="tx-advice" >
    <!--配置事务参数-->
    <tx:attributes>
        <tx:method name="create*" propagation="REQUIRED" isolation="DEFAULT" read-only="false" timeout="10"/>
    </tx:attributes>
</tx:advice>

但如果我们自己设置的事务管理器 id 与默认值不同,则必须手动在 <tx:advice> 元素中通过 transaction-manager 参数指定。

事务属性配置

对于<tx:advice> 来说,事务属性是被定义在<tx:attributes> 中的,该元素可以包含一个或多个 <tx:method> 元素。

<tx:method> 元素包含多个属性参数,可以为某个或某些指定的方法(name 属性定义的方法)定义事务属性,如下表所示。

事务属性说明
propagation指定事务的传播行为。
isolation指定事务的隔离级别。
read-only指定是否为只读事务。
timeout表示超时时间,单位为“秒”;声明的事务在指定的超时时间后,自动回滚,避免事务长时间不提交会回滚导致的数据库资源的占用。
rollback-for指定事务对于那些类型的异常应当回滚,而不提交。
no-rollback-for指定事务对于那些异常应当继续运行,而不回滚。

4. 配置切点切面

<tx:advice> 元素只是定义了一个 AOP 通知,它并不是一个完整的事务性切面。我们在 <tx:advice> 元素中并没有定义哪些 Bean 应该被通知,因此我们需要一个切点来做这件事。

在 Spring 的 XML 配置中,我们可以利用 Spring AOP 技术将事务通知(tx-advice)和切点配置到切面中,配置内容如下。

<!--配置切点和切面-->
<aop:config>
    <!--配置切点-->
    <aop:pointcut id="tx-pt" expression="execution(* net.biancheng.c.service.impl.OrderServiceImpl.*(..))"/>
    <!--配置切面-->
    <aop:advisor advice-ref="tx-advice" pointcut-ref="tx-pt"></aop:advisor>
</aop:config>

在以上配置中用到了 aop 命名空间,这就是我们为什么在给工程导入依赖时要引入 spring-aop 等 Jar 包的原因 

代码演示:Spring基于XML实现事务管理 

Spring 基于注解实现 事务管理 (熟悉)

通过 <tx:advice> 元素极大的简化了 Spring 声明式事务所需的 XML 配置。但其实我们还可以通过另一种方式进行进一步的简化,那就是“使用注解实现事务管理”。

在 Spring 中,声明式事务除了可以使用 XML 实现外,还可以使用注解实现,以进一步降低代码之间的耦合度。下面介绍下通过注解是如何实现声明式事务管理。

1. 开启注解事务

tx 命名空间提供了一个 <tx:annotation-driven> 元素,用来开启注解事务,简化 Spring 声明式事务的 XML 配置。
<tx:annotation-driven> 元素的使用方式也十分的简单,我们只要在 Spring 的 XML 配置中添加这样一行配置即可。

<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

与 <tx:advice> 元素一样,<tx:annotation-driven> 也需要通过 transaction-manager 属性来定义一个事务管理器,这个参数的取值默认为 transactionManager。如果我们使用的事务管理器的 id 与默认值相同,则可以省略对该属性的配置,形式如下。

<tx:annotation-driven/>

通过 <tx:annotation-driven> 元素开启注解事务后,Spring 会自动对容器中的 Bean 进行检查,找到使用 @Transactional 注解的 Bean,并为其提供事务支持。

2. 使用 @Transactional 注解

@Transactional 注解是 Spring 声明式事务编程的核心注解,该注解既可以在类上使用,也可以在方法上使用。

@Transactional
public class XXX {

    @Transactional
    public void A(Order order) {
    ……
    }

    public void B(Order order) {
    ……
    }
}

若 @Transactional 注解在类上使用,则表示类中的所有方法都支持事务;若 @Transactional 注解在方法上使用,则表示当前方法支持事务。

Spring 在容器中查找所有使用了 @Transactional 注解的 Bean,并自动为它们添加事务通知,通知的事务属性则是通过 @Transactional 注解的属性来定义的。

@Transactional 注解包含多个属性,其中常用属性如下表。

事务属性说明
propagation指定事务的传播行为。
isolation指定事务的隔离级别。
read-only指定是否为只读事务。
timeout表示超时时间,单位为“秒”;声明的事务在指定的超时时间后,自动回滚,避免事务长时间不提交会回滚导致的数据库资源的占用。
rollback-for指定事务对于那些类型的异常应当回滚,而不提交。
no-rollback-for指定事务对于那些异常应当继续运行,而不回滚。

代码演示:Spring基于注解实现事务管理 

SpEL 表达式语言 (了解)

Spring Expression Language(简称 SpEL)是一种功能强大的表达式语言,支持运行时查询和操作对象图 。表达式语言一般是用最简单的形式完成最主要的工作,以此减少工作量。

Java 有许多可用的表达式语言,例如 JSP EL,OGNL,MVEL 和 JBoss EL,SpEL 语法类似于 JSP EL,功能类似于 Struts2 中的 OGNL,能在运行时构建复杂表达式、存取对象图属性、调用对象方法等,并且能与 Spring 功能完美整合,如 SpEL 可以用来配置 Bean 定义。

SpEL 并不与 Spring 直接相关,可以被独立使用。SpEL 表达式的创建是为了向 Spring 社区提供一种受良好支持的表达式语言,该语言适用于 Spring 家族中的所有产品。也就是说,SpEL 是一种与技术无关的 API,可以集成其它表达式语言。

SpEL 提供了以下接口和类:

  • Expression interface:该接口负责评估表达式字符串
  • ExpressionParser interface:该接口负责解析字符串
  • EvaluationContext interface:该接口负责定义上下文环境

SpEL 支持如下表达式:

1. 基本表达式

字面量表达式、关系、逻辑与算术运算表达式、字符串连接及截取表达式、三目运算表达式、正则表达式、括号优先级表达式;

2. 类相关表达式

类类型表达式、类实例化、instanceof 表达式、变量定义及引用、赋值表达式、自定义函数、对象属性存取及安全导航表达式、对象方法调用、Bean 引用;

3. 集合相关表达式

内联 List、内联数组、集合、字典访问、列表、字典、数组修改、集合投影、集合选择;不支持多维内联数组初始化;不支持内联字典定义;

4. 其他表达式

模板表达式。

注:SpEL 表达式中的关键字不区分大小写。

代码演示:Spring SpEL表达式语言 

ThreadLocal 是什么?原理是什么?有哪些使用场景? (了解 )

什么是ThreadLocal?

ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

package com.javaBase.LineDistance;
 
/**
 * 〈一句话功能简述〉;
 * 〈功能详细描述〉
 *
 * @author jxx
 * @see [相关类/方法](可选)
 * @since [产品/模块版本] (可选)
 */
public class TestThreadLocal {
 
    public static void main(String[] args) {
 
        ThreadLocal<Integer> threadLocal = new MyThreadLocal();
 
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 3; i++) {
                    threadLocal.set(threadLocal.get() + 1);
                    System.out.println("线程1:" + threadLocal.get());
                }
            }
        });
 
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 3; i++) {
                    threadLocal.set(threadLocal.get() + 1);
                    System.out.println("线程2:" + threadLocal.get());
                }
            }
        });
 
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 3; i++) {
                    threadLocal.set(threadLocal.get() + 1);
                    System.out.println("线程3:" + threadLocal.get());
                }
            }
        });
 
        t1.start();
        t2.start();
        t3.start();
    }
 
    private static class MyThreadLocal extends ThreadLocal<Integer> {
 
        @Override
        protected Integer initialValue() {
            return 0;
        }
    }
 
}
线程2:1
线程1:1
线程2:2
线程3:1
线程1:2
线程3:2
线程2:3
线程3:3
线程1:3

可知个线程之间对ThreadLocal的操作互不影响。

ThreadLocal原理

ThreadLocal中的几个主要方法:

  • void set(Object value)设置当前线程的线程局部变量的值。
  • public Object get()该方法返回当前线程所对应的线程局部变量。
  • public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
  • protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
/**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
 
/**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

实现原理:ThreadLocal类通过操作每一个线程特有的ThreadLocalMap副本来实现线程隔离,ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取(对比Map对象来理解),每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。从而实现了变量访问在不同线程中的隔离。因为每个线程的变量都是自己特有的,完全不会有并发错误。还有一点就是,ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象,而值就是你所设置的对象了。

ThreadLocal的应用场景

1、方便同一个线程使用某一对象,避免不必要的参数传递;
2、线程间数据隔离(每个线程在自己线程里使用自己的局部变量,各线程间的ThreadLocal对象互不影响);
3、获取数据库连接、Session、关联ID(比如日志的uniqueID,方便串起多个日志);
其中spring中的事务管理器就是使用的ThreadLocal:
Spring的事务管理器通过AOP切入业务代码,在进入业务代码前,会依据相应的事务管理器提取出相应的事务对象,假如事务管理器是DataSourceTransactionManager,
就会从DataSource中获取一个连接对象,通过一定的包装后将其保存在ThreadLocal中。而且Spring也将DataSource进行了包装,重写了当中的getConnection()方法,或者说
该方法的返回将由Spring来控制,这样Spring就能让线程内多次获取到的Connection对象是同一个。

常用注解(熟悉)

@Configuration和@Bean

 从Spring 3.0开始,Spring JavaConfig项目提供的许多功能成为Spring Framework核心的一部分。因此,可以使用Java而不是XML文件来定义应用程序类外部的bean。基于Java类的配置的核心是带@Configuration(类似于XML中的<beans/>)注解的类和带@Bean(类似于XML中的<bean/>)
注解的方法。
  • @Bean注解标识一个方法,这个方法实例化、配置并且初始化一个由Spring IoC容器进行管理的新对象。@Bean可以对任何使用Spring @Component注解的类中的方法使用,但是最常与@Configuration一起使用。
  • 使用@Configuration的类表示其是作为Bean定义的来源,也就是定义这个类为配置类,此外,@Configuration类允许通过调用类中其他@Bean方法来定义Bean之间的依赖关系。
@Configuration
public class BeanConfiguration {
 
    @Bean
    public Account account(){
        return new Account("001001001");
    }
 
    @Bean
    public User user(Account account){
        return new User("张三",18,account);
    }
 
    @Bean
    public User userNoAccount(){
        return new User("张三",18,null);
    }
}
通过AnnotationConfigApplicationContext实例化Spring容器,这种通用的ApplicationContext实现方式不仅可以接受@Configuration类作为输入,还可以接受普通@Component类和使用JSR-330元数据注解的类。当@Configuration类作为输入时,@Configuration类和该类中所有@Bean的方法都会被注册为Bean Definition。当@Component类和JSR-330类作为输入时,它们被注册为Bean Definition,并且假定必要时在这些类中使用例如@Autowired或@Inject的DI元数据
@Test
public void configurationTest(){
    ApplicationContext ctx = new AnnotationConfigApplicationContext(BeanConfiguration.class);
    User user = ctx.getBean("user",User.class);
    User userNoAccount = ctx.getBean("userNoAccount",User.class);
    System.out.println(user);
    System.out.println(userNoAccount);
}
 
输出结果:
User(name=张三, age=18, account=Account(number=001001001))
User(name=张三, age=18, account=null)

@Scope

@Scope注解是springIoc容器中的一个作用域,在 Spring IoC 容器中具有以下几种作用域:基本作用域singleton(单例)、prototype(多例),Web 作用域(reqeust、session、globalsession),自定义作用域,使用方法就是直接在bean对象方法上增加@Scope注解

@Scope 作用域类型

 @Scope("singleton")

单实例属于默认作用域,IOC容器启动的时候就会调用方法创建对象,以后每次获取都是从Spring容器当中拿同一个对象(map当中)。

 @Scope("prototype")

多实例,在IOC容器启动创建的时候,并不会直接创建对象放在容器中去,当你需要调用的时候,才会从容器当中获取该对象然后进行创建。

@Scope("request")

同一个请求创建一个实例

@Scope("session")

同一个session创建一个实例

@Scope("globalsession")

同一个globalsession创建一个实例

@Scope注解的使用场景

几乎90%以上的业务使用singleton单实例就可以,所以spring默认的类型也是singleton,singleton虽然保证了全局是一个实例,对性能有所提高,但是如果实例中有非静态变量时,会导致线程安全问题,共享资源的竞争

当设置为prototype时:每次连接请求,都会生成一个bean实例,也会导致一个问题,当请求数越多,性能会降低,因为创建的实例,导致GC频繁,gc时长增加

@Lazy(true)

Spring IoC (ApplicationContext) 容器一般都会在启动的时候实例化所有单实例 bean 。如果我们想要 Spring 在启动的时候延迟加载 bean,即在调用某个 bean 的时候再去初始化,那么就可以使用 @Lazy 注解。

public class LazyConfig {
    @Lazy(true)
    @Bean
    public Person person() {
        return new Person("李四", 55);
    }
}

@Lazy用于指定该Bean是否取消预初始化。主要用于修饰Spring Bean类,用于指定该Bean的预初始化行为,使用时可以指定一个boolean型的value属性,该属性决定是否要预初始化该Bean

  • lazy代表延时加载,lazy=false,代表不延时,如果对象A中还有对象B的引用,会在A的xml映射文件中配置b的对象引用,多对一或一对多,不延时代表查询出对象A的时候,会把B对象也查询出来放到A对象的引用中,A对象中的B对象是有值的。
  • lazy=true代表延时,查询A对象时,不会把B对象也查询出来,只会在用到A对象中B对象时才会去查询,默认是false,一般需要优化效率的时候会用到

@Service、@Controller、@Repository、@Component

  • @Repository //作用在类上,表示数据层,此类的对象创建交由Spring管理
  • @Service //作用在类上,表示Service层,此类的对象创建交由Spring管理
  • @Controller //作用在类上,表示Controller,此类的对象创建交由Spring管理
  • @Component //作用在类上,表示普通层=类 ,此类的对象创建交由Spring管理

@PostConstruct和@PreDestory

一、定义:

@PostContruct是spring框架的注解,在方法上加该注解会在项目启动的时候执行该方法,也可以理解为在spring容器初始化的时候执行该方法。
从Java EE5规范开始,Servlet中增加了两个影响Servlet生命周期的注解,@PostConstruct@PreDestroy,这两个注解被用来修饰一个非静态的void()方法。

用法:

@PostContruct
public void method(){
// ....
}

或者:

public @PostContruct void method(){
// ....
}

二、作用

@PostConstruct注解的方法在项目启动的时候执行这个方法,也可以理解为在spring容器启动的时候执行,可作为一些数据的常规化加载。被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器调用一次,类似于Servlet的inti()方法。被@PostConstruct修饰的方法会在构造函数之后,init()方法之前运行。

三、执行顺序:

其实从依赖注入的字面意思就可以知道,要将对象b注入到对象a,那么首先就必须得生成对象a和对象b,才能执行注入。所以,如果一个类A中有个成员变量b被@Autowried注解,那么@Autowired注入是发生在A的构造方法执行完之后的。

如果想在生成对象时完成某些初始化操作,而偏偏这些初始化操作又依赖于依赖注入,那么就无法在构造函数中实现。为此,可以使用@PostConstruct注解一个方法来完成初始化,@PostConstruct注解的方法将会在依赖注入完成后被自动调用。

Constructor > @Autowired >>@PostConstruct构造方法 >依赖注入 >后期构造

四、特点

1.只有一个非静态方法才能使用此注解
2.被注解的方法不得有任何参数
3.被注解的方法返回值必须为void
4.被注解方法不得抛出已检查异常
5.此方法只会被执行一次死

@PreDestroy说明

被@PreDestroy修饰的方法会在服务器卸载Servlet的时候运行,并且只会被服务器调用一次,类似于Servlet的destroy()方法。被@PreDestroy修饰的方法会在destroy()方法之后运行,在Servlet被彻底卸载之前。

需要注意的是,注解会多多少少的影响到服务器的启动速度。服务器在启动时会遍历Web应用的WEB-INF/classess下的所有class文件与WEB-INF/lib下的所有jar文件,以检查哪些类使用了注解。如果所有应用程序中没有任何注解,可以在Web.xml中设置的metadata-complete
属性设置为true。(支持@PostConstruct和@PreDestroy的服务器需要支持Servlet2.5规范。Tomcat5.x仅支持Servlet2.4规范)

@Autowired、@Qualifier、@Resource()

  • @Autowired //作用在对象属性上,给当前属性赋值,在Spring根据类型查找,相同类型对象赋值
  • @Qualifier("对象名") //作用在对象属性上,一般用于和@Autowired配合使用,当@Autowired在Spring容器中发现两个和当前属性对象相同的时候,使用此注解,根据对象名称来赋值
  • @Autowired是Spring的注解;@Resource是javax.annotation注解,而是来自于JSR-250,J2EE提供,需要JDK1.6及以上。
  • @Autowired()通过byType的方式实现,默认情况下,其依赖的对象必须存在(bean可用),如果允许null值,可以设置它的required属性为false,如果容器中包含多个同一类型的Bean,那么启动容器时会报找不到指定类型bean的异常,解决办法就是结合 @Qualifier注解一起使用来指定使用对应名称的bean。
  • @Resource()默认通过byName的方式实现,如果找不到名字,则通过byType实现,如果两种都找不到就报错了,@Resource有两个重要的属性:name和type,如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不制定name也不制定type属性,这时将通过反射机制使用byName自动注入策略。
  • 有多个相同类型的 bean 并希望仅使用装配其中一个 bean 时,您可以使用@Qualifier 注解来指定使用哪一个bean,说白了就是当有很多相同类型的bean,通过这个注解可以通过bean的名称来指定使用哪个bean,通过是和 @Autowired 通过指定应该装配哪个确切的 bean 来消除歧义。

Spring事务的实现原理(了解)

一般我们很少使用编程式事务,更多的是使用@Transactional注解实现。当使用了@Transactional注解后事务的自动功能就会关闭,由spring帮助实现事务的控制。

Spring的事务管理是通过AOP代理实现的,对被代理对象的每个方法进行拦截,在方法执行前启动事务,在方法执行完成后根据是否有异常及异常的类型进行提交或回滚。

Spring AOP动态代理机制

Spring在运行期间会为目标对象生成一个代理对象,并在代理对象中实现对目标对象的增强。

原理:当在某个类或者方法上使用@Transactional注解后,spring会基于该类生成一个代理对象,并将这个代理对象作为bean。当调用这个代理对象的方法时,如果有事务处理,则会先关闭事务的自动功能,然后执行方法的具体业务逻辑,如果业务逻辑没有异常,那么代理逻辑就会直接提交,如果出现任何异常,那么直接进行回滚操作。当然我们也可以控制对哪些异常进行回滚操作。

底层原理图:

好文参考:【技术干货】Spring事务原理一探 - 知乎 

事务失效的情况(了解)

数据库引擎不支持事务

这里以 MySQL 为例,其 MyISAM 引擎是不支持事务操作的,InnoDB 才是支持事务的引擎,一般要支持事务都会使用 InnoDB。

没有被 Spring 管理 

如果此时把 @Service 注解注释掉,这个类就不会被加载成一个 Bean,那这个类就不会被 Spring 管理了,事务自然就失效了。

方法不是 public 的

@Transactional只有标注在public级别的方法上才能生效,对于非public方法将不会生效。这是由于Spring AOP不支持对private、protect方法进行拦截。如果需要对protect或private方法拦截则建议使用AspectJ。

声明 @Transactional 的目标方法时,Spring Framework 默认使用 AOP 代理,在代码运行时生成一个代理对象,再由这个代理对象来统一管理。声明式事务原理是Spring事务会为@Transaction标注的方法的类生成AOP增强的动态代理类对象,并且在调用目标方法的拦截链中加入TransactionInterceptor进行环绕增加,实现事务管理。从原理上来说,动态代理是通过接口实现,所以自然不能支持private和protect方法的。而CGLIB是通过继承实现,其实是可以支持protect方法的拦截的,但Spring AOP中并不支持这样使用。

 自身调用问题

当通过在同一个类的内部方法直接调用带有@Transactional的方法时,@Transactional将失效,例如:

public void saveAB(A a, B b)
{
    saveA(a);
    saveB(b);
}

@Transactional
public void saveA(A a)
{
    dao.saveA(a);
}

@Transactional
public void saveB(B b)
{
    dao.saveB(b);
}

在saveAB中调用saveA和saveB方法,两者的@Transactional都将失效。这是因为Spring事务的实现基于代理类,当在内部直接调用方法时,将不会经过代理对象,而是直接调用目标对象的方法,无法被TransactionInterceptor拦截处理。解决办法:
(1)ApplicationContextAware
通过ApplicationContextAware注入的上下文获得代理对象。

public void saveAB(A a, B b)
{
    Test self = (Test) applicationContext.getBean("Test");
    self.saveA(a);
    self.saveB(b);
}
**(2)AopContext**
通过AopContext获得代理对象。
public void saveAB(A a, B b)
{
    Test self = (Test)AopContext.currentProxy();
    self.saveA(a);
    self.saveB(b);
}

(2)@Autowired
通过@Autowired注解注入代理对象。 

@Component
public class Test {

    @Autowired
    Test self;

    public void saveAB(A a, B b)
    {
        self.saveA(a);
        self.saveB(b);
    }
    // ...
}

(3)拆分
将saveA、saveB方法拆分到另一个类中。

public void saveAB(A a, B b)
{
    txOperate.saveA(a);
    txOperate.saveB(b);
}

数据源没有配置事务管理器

​​​​​​​

如上面所示,当前数据源若没有配置事务管理器,那也是白搭!

不支持事务 

Propagation.NOT_SUPPORTED: 表示不以事务运行,当前若存在事务则挂起。都主动不支持以事务方式运行了,那事务生效也是白搭!

catch异常无法回滚

我们说只有抛出非检查异常或是rollbackFor中指定的异常才能触发回滚。如果我们把异常catch住,而且没抛出,则会导致无法触发回滚,这也是开发中常犯的错误。

@Transactional
public void insert(List<User> users) {
    try {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        for (User user : users) {
            String insertUserSql = "insert into User (id, name) values (?,?)";
            jdbcTemplate.update(insertUserSql, new Object[] { user.getId(),
                                                             user.getName() });
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

这里由于catch住了所有Exception,并且没抛出。当插入发生异常时,将不会触发回滚。
但同时我们也可以利用这种机制,用try-catch包裹不用参与事务的数据操作,例如对于写入一些不重要的日志,我们可将其用try-catch包裹,避免抛出异常,则能避免写日志失败而影响事务的提交。

异常类型错误

在默认情况下,抛出非检查异常会触发回滚,而检查异常不会。
根据invokeWithinTransaction方法,我们可以知道异常处理逻辑在completeTransactionAfterThrowing方法中,其实现如下:
根据rollbackOn判断异常是否为回滚异常。只有RuntimeException和Error的实例,即非检查异常,或者在@Transaction中通过rollbackFor属性指定的回滚异常类型,才会回滚事务。否则将继续提交事务。所以如果需要对检查异常进行回滚,需要记得指定rollbackFor属性,不然将回滚失效。

检查异常是Exception的本身或者子类:
例如:IOException(输入输出异常)、FileNotFoundException(文件没发现异常)、SQLException(SQL异常)
非检查异常是RuntimeException的本身或子类:
例如:算数异常(ArithmeticException)、空指针异常(NullPointerException),数组越界异常(ArrayIndexOutOfBoundException)

不同类中调用方未开始事务

在不同类之间的方法调用中,如果 A 方法开启了事务,B 方法没有开启事务,B 方法调用了 A 方法。如果 B 方法中发生异常,但不是调用的 A 方法产生的,则异常不会使 A 方法的事务回滚,此时事务无效。如果 B 方法中发生异常,异常是调用的 A 方法产生的,则 A 方法的事务回滚,此时事务有效。在 B 方法上加上注解 @Trasactional,这样 A 和 B 方法就在同一个事务里了,不管异常产生在哪里,事务都是有效的。
简单地说,不同类之间方法调用时,异常发生在无事务的方法中,但不是被调用的方法产生的,被调用的方法的事务无效。只有异常发生在开启事务的方法内,事务才有效。
 

以上总结了事务失效的场景,其实发生最多就是自身调用、异常被吃、异常抛出类型不对这三个了 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/139424.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【原创】升级需谨慎,开发两行泪!——记一次MySQL驱动包升级引发的事故

一、背景最近项目组在版本迭代时&#xff0c;组件也要进行升级&#xff0c;此时涉及到MySQL驱动包jdbc的版本升级。即从5.1.X升级到8.0.X。然鹅在上线之后就出现了一部分兼容性问题&#xff0c;造成了一次“事故”&#xff1a;调用接口出现“系统错误”。查看日志&#xff1a;j…

C. Least Prefix Sum(可以后悔的拿取+multiset)

Problem - C - Codeforces 波罗的海&#xff0c;一个著名的棋手&#xff0c;同时也是一个数学家&#xff0c;他有一个数组a1,a2,...,an&#xff0c;他可以进行以下几次&#xff08;可能是0次&#xff09;操作。 选择某个索引i&#xff08;1≤i≤n&#xff09;。 将ai与-1相乘&…

Ffmpeg中AVFrame数据保存成YUV--讨论AVFrame的linesize

目录 YUV播放器 AVFrame中保存成YUV实现 linesize的意义 实测(PC机-64bits-win10) 总结&#xff1a; YUV播放器 首先要有一个YUVplayer用来播放测试的YUV数据&#xff0c;雷神改良过的YUV播放器&#xff1a; 修改了一个YUV/RGB播放器_雷霄骅的博客-CSDN博客 播放器播放界…

查询是: LOCK TABLE test.xx_test IN ACCESS SHARE MODE问题解决办法

如题所示&#xff0c;这个问题是我在postgresql中使用pg_dump备份多个schema的表时遇到的问题。bin\pg_dump --dbnamepostgresql://dbuser:123456localhost:5432/test --table public.xx_user --table test.xx_test -f d:\tools\pgsql\dump.sql pg_dump: 错误: 查询失败: 閿欒…

react生命周期(类组件/函数组件)

1.react代码模式分为两种 类组件和函数组件&#xff08;生命周期也有所不同&#xff09; 2.类组件&#xff08;写法如下&#xff09; import React from react export default class App1 extends React.Component{state {username:,password:}setUser (event) > {this.s…

《MySQL系列-InnoDB引擎12》启动、关闭与恢复

启动、关闭与恢复 InnoDB是MySQL数据库得存储引擎之一&#xff0c;因此InnoDB存储引擎得启动和关闭&#xff0c;可以说是MySQL实例得启动过程中对InnoDB存储引擎的处理过程。 在关闭时&#xff0c;参数innodb_fast_shutdown影响着表的存储引擎为InnoDB的行为。该参数可取值为0、…

BIOS(控制权交接第一棒)

计算机系统的控制权&#xff08;CPU的使用权&#xff09;交接的第一棒是BIOS&#xff01;接下来&#xff0c;我们简单学习一下BIOS相关知识。 在计算机接电后按下开机键&#xff0c;首先运行的软件是基本输入输出系统&#xff08;Basic Input Output System&#xff0c;BIOS&a…

阿里云故障有感

2304阿里云故障有感 为什么会写一个如此奇怪的议题。 人的生活轨迹&#xff0c;不一定只有唯一的叙事方式&#xff0c;可以不那么苦&#xff0c;不那么累&#xff0c;不那么怕&#xff0c;不那么憋屈。 不再去追求宏大的叙事&#xff0c;而是去关心自己的感受&#xff0c;吃没…

Spring Boot学习篇(六)

Spring Boot学习篇(六) mybatis-plus使用篇(二) 1 新增 1.1 示例代码 AutowiredISongsService songsService; Testpublic void add(){Songs snew Songs(null,"11","xx",null,"xx歌曲","2020-02-12");songsService.save(s);System.…

linux之vim编辑器

文章目录1. 概念2.安装3. vim的使用1.使用一些按键没有反应2. 模式转换1.命令模式转换到插入模式1. a2. i3. o2.命令模式切换到底行模式1. 退出3. 整体概念图4. 热键的使用1. 命令模式下1.复制——yy 粘贴——p复制若干行—— n yy粘贴若干行—— n p2. 撤销操作——u3. 对撤销…

03-Alibaba微服务组件Nacos注册中心实战

什么是 Nacos Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集&#xff0c;帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。 Nacos 的关键特性包括: 服务发现和服务健康监测 动态配置服务 动态 DNS 服务 服务及其元数据管理 Nacos…

陆道培医疗冲刺香港上市:持续亏损,毛利率下滑,陆佩华为董事长

2023年1月3日&#xff0c;陆道培医疗集团&#xff08;下称“陆道培医疗”&#xff09;在港交所递交招股书&#xff0c;准备在港交所主板上市。本次冲刺上市&#xff0c;陆道培医疗拟将募资金额用于河北燕达陆道培医院的扩建及搬迁及上海陆道培医院的开业等。 同时&#xff0c;陆…

新生儿只是睡?了解了他的生长发育情况后就不这么觉得了

在网上看到这样一句话&#xff0c;说:所谓坐月子&#xff0c;其实就是把最需要照顾的产妇和新生儿捆在一起&#xff0c;让他们互相治愈。是的&#xff0c;在很多人眼里&#xff0c;新生儿除了吃饭就是睡觉&#xff0c;照顾起来真的很容易&#xff0c;但是只要了解了新生儿的生长…

Android开发常见问题:为什么不要用Timer?

前言 从刚开始做Android开始&#xff0c;就时刻谨记一条规则&#xff1a;如果有能实现功能的Android API&#xff0c;就不要用Java API。所以我很少用Timer&#xff0c;直到前一阵一个朋友问我&#xff1a; 我有一个Timer计时器&#xff0c;我看logcat已经启动了&#xff0c;为…

嵌入式应该从哪里入手?

就从目前IT行业大的方向上来讲嵌入式工程师相对来讲是非常吃香的&#xff0c;学习嵌入式开发的朋友数量也是非常的多的&#xff0c;那么对于对嵌入式工程师我们应该从哪里入手呢? 下面就根据我自身多年的一个实际的工作经验来给哪些不知道怎么入门的朋友来简单的介绍一下。 前…

C语言进阶——指针(一)

目录 一. 字符指针 二. 指针数组 三. 数组指针 四. 数组参数、指针参数 1.一维数组传参 2.二维数组传参 3.一级指针传参 4.二级指针传参 一. 字符指针 在之前&#xff0c;我们就了解到过字符指针 int main() {char aW;//字符变量char* pa&a;//字符指针*…

1.3日报

今天优化了getMobileByScene接口&#xff0c; 测试accountadd接口 遇到的问题与解决 在升级安装python3时&#xff0c;由于操作失误&#xff0c;导致系统错误&#xff0c;在同事帮助下重装了系统&#xff0c;好在保住了文件。但是软件还得重新配置。 getMobileByScene接口在…

精益|什么是价值流图分析(VSM)?

在精益生产管理中&#xff0c;价值流研究主要是指利用制作价值流图&#xff0c;进行价值流图研究可以发现并消除浪费、降低成本&#xff0c;改进企业运营状况&#xff0c;提升企业竞争力。因此进行价值流研究离不开价值流图。 价值流图&#xff08;Value Stream Mapping&#x…

基于KT6368A蓝牙芯片开发智能抖音翻页翻页笔总结

一、功能简介 KT6368A蓝牙芯片也是基于 HID 开发&#xff0c;主要用于浏览当下火爆的抖音等小视频的上下翻页、左右菜单切换、暂停等操作。打开手机蓝牙进行连接&#xff0c;进入视频浏览界面操作对应按键即可。包含一个蓝牙的指示灯&#xff0c;表示是否连接 。同时支持adkey按…

HBase面试题汇总

1、请描述HBse的&#xff1f; 答&#xff1a; Memstore级别&#xff1a;当MemStore的大小达到设置阈值&#xff08;默认128M&#xff09;&#xff0c;会触发flush操作。 1、HBase中Memstore在何时进行数据的flush操作&#xff1f; 答&#xff1a; Memstore级别&#xff1a…