spring

目录

1.Spring是什么

2.Spring体系结构

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

2. Web模块

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

4. AOP、Aspects、Instrumentation和Messaging

5. Test模块

3.Spring开发环境搭建

安装JDK

下载Eclipse IDE

下载Apache Common Logging API

下载Spring

4.第一个Spring程序

1. 创建Java项目

2. 添加jar包

3. 创建Java类

4. 创建配置文件

5. 运行程序

5.Spring IoC容器

1. BeanFactory 容器

2. ApplicationContext 容器

6.Spring Bean定义

7.Spring Bean作用域

作用域的种类

singleton

prototype

8.Spring Bean生命周期

Spring Bean生命周期执行流程

初始化回调

销毁回调

示例

默认的初始化和销毁方法

9.BeanPostProcessor(Spring后置处理器)

示例

10.Spring Bean继承

示例

Bean定义模板

11.Spring依赖注入

构造函数注入

setter注入

12.Spring注入内部Bean

示例

13.Spring注入集合

示例

注入Bean引用

注入null和空字符串的值

14.Spring Bean自动装配

示例

自动装配的优缺点

15.Spring基于注解装配Bean

示例

16.Spring AOP(面向切面编程)

为什么使用AOP

AOP术语

17.Spring JDK动态代理

示例

18.Spring CGLlB动态代理

示例

JDK代理和CGLIB代理的区别

19.Spring集成AspectJ

AspectJ包下载缓慢解决方法

20.Spring AOP:基于AspectJ XML开发

定义切面

定义切入点

定义通知

示例

21.Spring AOP:基于AspectJ注解开发

定义切面@Aspect

定义切入点@Pointcut

定义通知advice

示例

22.Spring JdbcTemplate类

示例

23.Spring集成Log4J

示例

24.Spring事务(Transaction)

编程式和声明式

事务管理接口

PlatformTransactionManager接口

TransactionDefinition接口

TransactionStatus接口

25.Spring编程式事务管理

示例

26.Spring基于XML实现事务管理

示例

27.Spring基于注解实现事务管理

示例

28.SpEL表达式语言(Spring)

示例

SpEL对Bean定义的支持

SpEL中的运算符

SpEL中的变量


1.Spring是什么

Spring 自诞生以来一直备受青睐,它包括许多框架,例如 Spring framework、SpringMVC、SpringBoot、Spring Cloud、Spring Data、Spring Security 等,所以有人将它们亲切的称之为:Spring 全家桶。

Spring framework 就是我们平时说的 Spring 框架。

Spring 框架是全家桶内其它框架的基础和核心,下面让我们来认识 Spring 框架吧。

Spring 是目前主流的 Java Web 开发框架,是 Java 世界最为成功的框架。该框架是一个轻量级的开源框架,具有很高的凝聚力和吸引力。

Spring 由 Rod Johnson 创立,2004 年发布了 Spring 框架的第一版,其目的是用于简化企业级应用程序开发的难度和周期。

Spring 是分层的 Java SE/EE 一站式轻量级开源框架,以 IoC(Inverse of Control,控制反转)和 AOP(Aspect Oriented Programming,面向切面编程)为内核

IoC 指的是将对象的创建权交给 Spring 去创建。使用 Spring 之前,对象的创建都是由我们使用 new 创建,而使用 Spring 之后,对象的创建都交给了 Spring 框架

AOP 用来封装多个类的公共行为,将那些与业务无关,却为业务模块所共同调用的逻辑封装起来,减少系统的重复代码,降低模块间的耦合度。另外,AOP 还解决一些系统层面上的问题,比如日志、事务、权限等。

在 Spring 中,认为一切 Java 类都是资源,而资源都是类的实例对象(Bean),容纳并管理这些 Bean 的是 Spring 所提供的 IoC 容器,所以 Spring 是一种基于 Bean 的编程,它深刻地改变着 Java 开发世界,使用基本的 JavaBean 来完成以前只有 EJB 才能完成的工作,使得很多复杂的代码变得优雅和简洁,避免了 EJB 臃肿、低效的开发模式,极大的方便项目的后期维护、升级和扩展。迅速地取代 EJB 成为了实际的开发标准。

在实际开发中,服务器端通常采用三层体系架构,分别为表现层(web)、业务逻辑层(service)、持久层(dao)。

Spring 致力于 Java EE 应用各层的解决方案,对每一层都提供了技术支持

在表现层提供了与 Spring MVC、Struts2 框架的整合,在业务逻辑层可以管理事务和记录日志等,在持久层可以整合 MyBatis、Hibernate 和 JdbcTemplate 等技术。这就充分体现出 Spring 是一个全面的解决方案,对于已经有较好解决方案的领域,Spring 绝不做重复的事情。

从某个程度上来看,Spring 框架充当了黏合剂和润滑剂的角色,能够将相应的 Java Web 系统柔顺地整合起来,并让它们更易使用。同时其本身还提供了声明式事务等企业级开发不可或缺的功能。

从设计上看,Spring 框架给予了 Java 程序员更高的自由度,对业界的常见问题也提供了良好的解决方案,因此,在开源社区受到了广泛的欢迎,并且被大部分公司作为 Java 项目开发的首选框架。

Spring 作为实现 Java EE 的一个全方位应用程序框架,为开发企业级应用提供了一个健壮、高效的解决方案。它不仅可以应用于服务器端开发,也可应用于任何 Java 应用的开发。

Spring 框架具有以下几个特点。

1)方便解耦,简化开发

Spring 就是一个大工厂,可以将所有对象的创建和依赖关系的维护交给 Spring 管理。

2)方便集成各种优秀框架

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

3)降低 Java EE API 的使用难度

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

4)方便程序的测试

Spring 支持 JUnit4,可以通过注解方便地测试 Spring 程序。

5)AOP 编程的支持

Spring 提供面向切面编程,可以方便地实现对程序进行权限拦截和运行监控等功能。

6)声明式事务的支持

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

2.Spring体系结构

Spring 框架采用分层的理念,根据功能的不同划分成了多个模块,这些模块大体可分为 Data Access/Integration(数据访问与集成)、Web、AOP、Aspects、Instrumentation(检测)、Messaging(消息处理)、Core Container(核心容器)和 Test。如下图所示(以下是 Spring Framework 4.x 版本后的系统架构图)。
 

Spring体系结构图


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 请求的功能。

3.Spring开发环境搭建

本节主要介绍如何搭建 Spring 开发环境,使用 Spring 之前需要安装 JDK、Tomcat 和 Eclipse。

安装JDK

JDK 安装和配置的详细步骤我们已经在《Java教程》中进行了介绍,本文只做简单描述,不了解的读者请转到:


您可以在 Oracle 网站上下载最新版本的 JDK,然后按照下载文件中的说明安装和配置 JDK。

配置环境变量需要修改 PATH 和 JAVA_HOME 的值,通常为 java_install_dir/bin(JDK安装目录下bin目录的路径)和 java_install_dir(JDK安装目录)。

对于 Windows 系统,您可以鼠标右键“我的电脑”,选择“属性”->“高级”->“环境变量”,修改 PATH 值,并单击“确定”按钮。

对于 Unix(Solaris、Linux 等)系统,如果 JDK 安装在 /usr/local/jdk 1.8.0_241 中,且使用的是 CShell,则需要将以下内容添加到 .cshrc 文件中。

setenv PATH /usr/local/jdk1.8.0_241/bin:$PATH
setenv JAVA_HOME /usr/local/jdk1.8.0_241 

下载Eclipse IDE

本教程中所有实例都是使用 Eclipse IDE 编写的,因此我们建议您安装一个最新版本的 IDE,本教程使用版本为 Eclipse 2019。

Eclipse 下载地址:Eclipse Downloads | The Eclipse Foundation

下载文件后,将压缩包解压到相应位置。例如 Windows 上的 C:\ eclipse、Linux / Unix 上的 /usr/local/eclipse。

在 Windows 中,可以通过以下 DOS 命令或者双击 eclipse.exe 来启动 Eclipse。

%C:\eclipse\eclipse.exe 

在 Unix(Solaris、Linux等)中,可执行以下命令来启动 Eclipse。

$/usr/local/eclipse/eclipse

下载Apache Common Logging API

Common Logging 是使用 Spring 的必要组件。

Apache Common Logging API 下载地址:Apache Commons Logging - Download Apache Commons Logging

下载完成后,将压缩包解压到相应位置。例如 Windows 上的 C:\ commons-logging-1.2、Linux / Unix 上的 /usr/local/commons-logging-1.2 等。该文件包含以下 jar 文件和其它支持文档,目录结构如下。
 

Common Logging目录结构

下载Spring

Spring 下载地址:JFrog

根据操作系统(Windows或Unix)下载相应的 Spring 压缩包。本教程使用版本为 spring-framework-5.2.3.RELEASE-dist.zip,该文件目录结构如下:
 

Spring目录结构


下面对上图所示的目录进行简单介绍,具体如表 1 所示。
 

表 1 Spring 的目录介绍
名称作用
docs包含 Spring 的 API 文档和开发规范
libs包含开发需要的 jar 包和源码包
schema包含开发所需要的 schema 文件,在这些文件中定义了 Spring 相关配置文件的约束


在 libs 目录中,包含了 Spring 框架提供的所有 jar 文件,其中有 4 个 jar 文件是 Spring 框架的基础包,分别对应 Spring 容器的四个模块,具体如表 2 所示。
 

表 2 Spring 依赖jar包介绍
名称作用
spring-core-x.x.xx.RELEASE.jar包含 Spring 框架基本的核心工具类,Spring 其他组件都要用到这个包中的类,是其他组件的基本核心。
spring-beans-x.x.xx.RELEASE.jar所有应用都要用到的,它包含访问配置文件、创建和管理 Bean 以及进行 Inversion of Control(IoC)或者 Dependency Injection(DI)操作相关的所有类。
spring-context-x.x.xx.RELEASE.jarSpring 提供在基础 IoC 功能上的扩展服务,此外还提供许多企业级服务的支持,如邮件服务、任务调度、JNDI 定位、EJB 集成、远程访问、缓存以及各种视图层框架的封装等
spring-expression-x.x.xx.RELEASE.jar定义了 Spring 的表达式语言。
需要注意的是,在使用 Spring 开发时,除了 Spring 自带的 JAR 包以外,还需要一个第三方 JAR 包 commons.logging 处理日志信息


使用 Spring 框架时,只需将 Spring 的 4 个基础包以及 commons-logging-1.2.jar 包复制到项目的 lib 目录,并发布到类路径中即可。

4.第一个Spring程序

1. 创建Java项目

在 Eclipse 中创建一个简单的 Java 项目,依次单击 File -> New -> Java Project,这里将项目名称设置为 HelloSpring。
 

创建HelloSpring项目

2. 添加jar包

在项目中添加 Spring 和 logging 的 jar 包。鼠标右键项目 HelloSpring,单击 Build Path -> Add external archives,选择 jar 包,点击完成。目录结构如下。
 

项目目录结构

运行该程序需要以下 3 个文件。您可以选择先导入以下文件,后面根据需求在添加相应的 jar 包,或直接导入所有 Spring 相关 jar 包。

  • org.springframework.core-5.2.3.RELEASE.jar
  • org.springframework.beans-5.2.3.RELEASE.jar
  • commons.logging-1.2.jar

3. 创建Java类

创建 net.biancheng 包,在该包下创建 HelloWorld.java 和 MainApp.java 类。

HelloWorld.java 类的代码如下。

 
  1. package net.biancheng;
  2. public class HelloWorld {
  3. private String message;
  4. public void setMessage(String message) {
  5. this.message = message;
  6. }
  7. public void getMessage() {
  8. System.out.println("message : " + message);
  9. }
  10. }

MainApp.java 类的代码如下。

 
  1. package net.biancheng;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class MainApp {
  5. public static void main(String[] args) {
  6. ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
  7. HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
  8. obj.getMessage();
  9. }
  10. }

关于以上代码,需要注意以下两点:

  1. 创建 ApplicationContext 对象时,我们使用了 ClassPathXmlApplicationContext 类。该类用于加载 Spring 配置文件、创建和初始化所有对象,也就是下面配置文件中提到的 Bean。
  2. ApplicationContext.getBean() 方法用来获取 Bean,该方法返回值类型为 Object,通过强制类型转换为 HelloWorld 的实例对象,根据该对象调用类中的方法。

4. 创建配置文件

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

 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  6. <bean id="helloWorld" class="net.biancheng.HelloWorld">
  7. <property name="message" value="Hello World!" />
  8. </bean>
  9. </beans>

您也可以将该配置文件命名为其它有效的名称。需要注意的是,该文件名必须与 MainApp.java 中读取的配置文件名称一致。

Beans.xml 用于给不同的 Bean 分配唯一的 ID,并给相应的 Bean 属性赋值。例如,在以上代码中,我们可以在不影响其它类的情况下,给 message 变量赋值。

5. 运行程序

运行 MainApp.java,Eclipse IDE 控制台中显示信息如下。

message : Hello World!

至此,我们就成功创建了第一个 Spring 应用程序。

5.Spring IoC容器

IoC 容器是 Spring 的核心,也可以称为 Spring 容器Spring 通过 IoC 容器来管理对象的实例化和初始化,以及对象从创建到销毁的整个生命周期。

Spring 中使用的对象都由 IoC 容器管理,不需要我们手动使用 new 运算符创建对象。由 IoC 容器管理的对象称为 Spring Bean,Spring Bean 就是 Java 对象,和使用 new 运算符创建的对象没有区别。

Spring 通过读取 XML 或 Java 注解中的信息来获取哪些对象需要实例化。

Spring 提供 2 种不同类型的 IoC 容器,即 BeanFactory 和 ApplicationContext 容器。

1. BeanFactory 容器

BeanFactory 是最简单的容器,由 org.springframework.beans.factory.BeanFactory 接口定义,采用懒加载(lazy-load),所以容器启动比较快。BeanFactory 提供了容器最基本的功能

为了能够兼容 Spring 集成的第三方框架(如 BeanFactoryAware、InitializingBean、DisposableBean),所以目前仍然保留了该接口。

简单来说,BeanFactory 就是一个管理 Bean 的工厂,它主要负责初始化各种 Bean,并调用它们的生命周期方法。

BeanFactory 接口有多个实现类,最常见的是 org.springframework.beans.factory.xml.XmlBeanFactory。

使用 BeanFactory 需要创建 XmlBeanFactory 类的实例,通过 XmlBeanFactory 类的构造函数来传递 Resource 对象。如下所示。

 
  • Resource resource = new ClassPathResource("applicationContext.xml");
  • BeanFactory factory = new XmlBeanFactory(resource);

2. ApplicationContext 容器

ApplicationContext 继承了 BeanFactory 接口,由 org.springframework.context.ApplicationContext 接口定义,对象在启动容器时加载。ApplicationContext 在 BeanFactory 的基础上增加了很多企业级功能,例如 AOP、国际化、事件支持等。

ApplicationContext 接口有两个常用的实现类,具体如下。

1)ClassPathXmlApplicationContext

该类从类路径 ClassPath 中寻找指定的 XML 配置文件,并完成 ApplicationContext 的实例化工作,具体如下所示。

 
  • ApplicationContext applicationContext = new ClassPathXmlApplicationContext(String configLocation);

在上述代码中,configLocation 参数用于指定 Spring 配置文件的名称和位置,如 Beans.xml。

2)FileSystemXmlApplicationContext

该类从指定的文件系统路径中寻找指定的 XML 配置文件,并完成 ApplicationContext 的实例化工作,具体如下所示。

 
  • ApplicationContext applicationContext = new FileSystemXmlApplicationContext(String configLocation);

它与 ClassPathXmlApplicationContext 的区别是:在读取 Spring 的配置文件时,FileSystemXmlApplicationContext 不会从类路径中读取配置文件,而是通过参数指定配置文件的位置。即 FileSystemXmlApplicationContext 可以获取类路径之外的资源,如“F:/workspaces/Beans.xml”。

通常在 Java 项目中,会采用 ClassPathXmlApplicationContext 类实例化 ApplicationContext 容器的方式,而在 Web 项目中,ApplicationContext 容器的实例化工作会交由 Web 服务器完成。Web 服务器实例化 ApplicationContext 容器通常使用基于 ContextLoaderListener 实现的方式,它只需要在 web.xml 中添加如下代码:

 
  1. <!--指定Spring配置文件的位置,有多个配置文件时,以逗号分隔-->
  2. <context-param>
  3. <param-name>contextConfigLocation</param-name>
  4. <!--spring将加载spring目录下的applicationContext.xml文件-->
  5. <param-value>
  6. classpath:spring/applicationContext.xml
  7. </param-value>
  8. </context-param>
  9. <!--指定以ContextLoaderListener方式启动Spring容器-->
  10. <listener>
  11. <listener-class>
  12. org.springframework.web.context.ContextLoaderListener
  13. </listener-class>
  14. </listener>

需要注意的是,BeanFactory 和 ApplicationContext 都是通过 XML 配置文件加载 Bean 的

二者的主要区别在于,如果 Bean 的某一个属性没有注入,使用 BeanFacotry 加载后,第一次调用 getBean() 方法时会抛出异常,而 ApplicationContext 则会在初始化时自检,这样有利于检查所依赖的属性是否注入。

因此,在实际开发中,通常都选择使用 ApplicationContext,只有在系统资源较少时,才考虑使用 BeanFactory。

6.Spring Bean定义

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

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

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

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


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

例如,《第一个Spring程序》一节中的 Beans.xml 配置文件,代码如下所示:

 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  6. <bean id="helloWorld" class="net.biancheng.HelloWorld">
  7. <property name="message" value="Hello World!" />
  8. </bean>
  9. </beans>

上述代码中,使用 id 属性定义了 Bean,并使用 class 属性指定了 Bean 对应的类

<bean> 元素中可以包含很多属性,其常用属性如下表所示。
 

<bean>元素的常用属性
属性名称描述
idBean 的唯一标识符,Spring 容器对 Bean 的配置和管理都通过该属性完成。id 的值必须以字母开始,可以使用字母、数字、下划线等符号。
namename 属性中可以为 Bean 指定多个名称,每个名称之间用逗号或分号隔开。Spring 容器可以通过 name 属性配置和管理容器中的 Bean。
class该属性指定了 Bean 的具体实现类,它必须是一个完整的类名,即类的全限定名。
scope 用于设定 Bean 实例的作用域,属性值可以为 singleton(单例)、prototype(原型)、request、session 和 global Session。其默认值是 singleton
constructor-arg<bean>元素的子元素,可以使用此元素传入构造参数进行实例化。该元素的 index 属性指定构造参数的序号(从 0 开始),type 属性指定构造参数的类型
property<bean>元素的子元素,用于调用 Bean 实例中的 setter 方法来属性赋值,从而完成依赖注入。该元素的 name 属性用于指定 Bean 实例中相应的属性名
ref<property> 和 <constructor-arg> 等元素的子元索,该元素中的 bean 属性用于指定对某个 Bean 实例的引用
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 时有效

7.Spring Bean作用域

在配置文件中,除了可以定义 Bean 的属性值和相互之间的依赖关系,还可以声明 Bean 的作用域。例如,如果每次获取 Bean 时,都需要一个 Bean 实例,那么应该将 Bean 的 scope 属性定义为 prototype,如果 Spring 需要每次都返回一个相同的 Bean 实例,则应将 Bean 的 scope 属性定义为 singleton。

作用域的种类

Spring 容器在初始化一个 Bean 实例时,同时会指定该实例的作用域。Spring 5 支持以下 6 种作用域。

1)singleton

默认值,单例模式,表示在 Spring 容器中只有一个 Bean 实例,Bean 以单例的方式存在。

2)prototype

原型模式,表示每次通过 Spring 容器获取 Bean 时,容器都会创建一个 Bean 实例。

3)request

每次 HTTP 请求,容器都会创建一个 Bean 实例。该作用域只在当前 HTTP Request 内有效。

4)session

同一个 HTTP Session 共享一个 Bean 实例,不同的 Session 使用不同的 Bean 实例。该作用域仅在当前 HTTP Session 内有效。

5)application

同一个 Web 应用共享一个 Bean 实例,该作用域在当前 ServletContext 内有效。

类似于 singleton,不同的是,singleton 表示每个 IoC 容器中仅有一个 Bean 实例,而同一个 Web 应用中可能会有多个 IoC 容器,但一个 Web 应用只会有一个 ServletContext,也可以说 application 才是 Web 应用中货真价实的单例模式。

6)websocket

websocket 的作用域是 WebSocket ,即在整个 WebSocket 中有效。

注意:Spring 5 版本之前还支持 global Session,该值表示在一个全局的 HTTP Session 中,容器会返回该 Bean 的同一个实例。一般用于 Portlet 应用环境。Spring 5.2.0 版本中已经将该值移除了。

request、session、application、websocket 和  global Session 作用域只能在 Web 环境下使用,如果使用 ClassPathXmlApplicationContext 加载这些作用域中的任意一个的 Bean,就会抛出以下异常。

java.lang.IllegalStateException: No Scope registered for scope name 'xxx'


下面我们详细讲解常用的两个作用域:singleton 和 prototype。

singleton

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

通常情况下,这种单例模式对于无会话状态的 Bean(如 DAO 层、Service 层)来说,是最理想的选择。

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

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

例 1

下面使用 Eclipse IDE 演示如何将 Bean 的作用域指定为 singleton,步骤如下:

  1. 创建 SpringDemo 项目,并在 src 目录下创建 net.biancheng 包。
  2. 添加相应的 jar 包,可以参考《第一个Spring程序》一节。
  3. 在 net.biancheng 包下创建 HelloWorld 和 MainApp 类。
  4. 在 src 目录下创建 Spring 配置文件 Beans.xml。
  5. 运行 SpringDemo 项目。


HelloWorld 类代码如下。

 
  1. package net.biancheng;
  2. public class HelloWorld {
  3. private String message;
  4. public void setMessage(String message) {
  5. this.message = message;
  6. }
  7. public void getMessage() {
  8. System.out.println("message : " + message);
  9. }
  10. }

MainApp 类如下。

 
  1. package net.biancheng;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class MainApp {
  5. public static void main(String[] args) {
  6. ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
  7. HelloWorld objA = (HelloWorld) context.getBean("helloWorld");
  8. objA.setMessage("对象A");
  9. objA.getMessage();
  10. HelloWorld objB = (HelloWorld) context.getBean("helloWorld");
  11. objB.getMessage();
  12. }
  13. }

Beans.xml 文件内容如下。

 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  6. <bean id="helloWorld" class="net.biancheng.HelloWorld" scope="singleton"/>
  7. </beans>

运行结果如下。

message : 对象A
message : 对象A

从运行结果可以看出,两次输出内容相同,这说明 Spring 容器只创建了一个 HelloWorld 类的实例。由于 Spring 容器默认作用域是 singleton,所以如果省略 scope 属性,其输出结果也会是一个实例。

prototype

对于 prototype 作用域的 Bean,Spring 容器会在每次请求该 Bean 时都创建一个新的 Bean 实例。prototype 作用域适用于需要保持会话状态的 Bean(如 Struts2 的 Action 类)。

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

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

例 2

在例 1 的基础上,修改配置文件 Beans.xml,内容如下。

 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  6. <bean id="helloWorld" class="net.biancheng.HelloWorld" scope="prototype"/>
  7. </beans>

运行结果如下。

message : 对象A
message : null

从运行结果可以看出,两次输出的内容并不相同,这说明在 prototype 作用域下,Spring 容器创建了两个不同的 HelloWorld 实例。

8.Spring Bean生命周期

在传统的 Java 应用中,Bean 的生命周期很简单,使用关键字 new 实例化 Bean,当不需要该 Bean 时,由 Java 自动进行垃圾回收。

Spring 中 Bean 的生命周期较复杂,可以表示为:Bean 的定义 -> Bean 的初始化 -> Bean 的使用 -> Bean 的销毁。

Spring 根据 Bean 的作用域来选择管理方式。对于 singleton 作用域的 Bean,Spring 能够精确地知道该 Bean 何时被创建,何时初始化完成,以及何时被销毁;而对于 prototype 作用域的 Bean,Spring 只负责创建,当容器创建了 Bean 的实例后,Bean 的实例就交给客户端代码管理,Spring 容器将不再跟踪其生命周期。

Spring Bean生命周期执行流程

Spring 容器在确保一个 Bean 能够使用之前,会进行很多工作。Spring 容器中 Bean 的生命周期流程如下图所示。
 

Bean的生命周期


图 1  Bean 的生命周期

Bean 生命周期的整个执行过程描述如下。

  1. Spring 启动,查找并加载需要被 Spring 管理的 Bean,并实例化 Bean。
  2. 利用依赖注入完成 Bean 中所有属性值的配置注入。
  3. 如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前 Bean 的 id 值。
  4. 如果 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂实例的引用。
  5. 如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。
  6. 如果 Bean 实现了 BeanPostProcessor 接口,则 Spring 调用该接口的预初始化方法 postProcessBeforeInitialzation() 对 Bean 进行加工操作,此处非常重要,Spring 的 AOP 就是利用它实现的。
  7. 如果 Bean 实现了 InitializingBean 接口,则 Spring 将调用 afterPropertiesSet() 方法。
  8. 如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。
  9. 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的初始化方法 postProcessAfterInitialization()。此时,Bean 已经可以被应用系统使用了。
  10. 如果在 <bean> 中指定了该 Bean 的作用域为 singleton,则将该 Bean 放入 Spring IoC 的缓存池中,触发 Spring 对该 Bean 的生命周期管理;如果在 <bean> 中指定了该 Bean 的作用域为 prototype,则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该 Bean。
  11. 如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法销毁 Bean;如果在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁。


Spring 为 Bean 提供了细致全面的生命周期过程,实现特定的接口或设置 <bean> 的属性都可以对 Bean 的生命周期过程产生影响。建议不要过多的使用 Bean 实现接口,因为这样会导致代码的耦合性过高。

了解 Spring 生命周期的意义就在于,可以利用 Bean 在其存活期间的指定时刻完成一些相关操作。一般情况下,会在 Bean 被初始化后和被销毁前执行一些相关操作。

Spring 官方提供了 3 种方法实现初始化回调和销毁回调:

  1. 实现 InitializingBean 和 DisposableBean 接口;
  2. 在 XML 中配置 init-method 和 destory-method;
  3. 使用 @PostConstruct 和 @PreDestory 注解。


在一个 Bean 中有多种生命周期回调方法时,优先级为:注解 > 接口 > XML。

不建议使用接口和注解,这会让 pojo 类和 Spring 框架紧耦合。

初始化回调

1. 使用接口

org.springframework.beans.factory.InitializingBean 接口提供了以下方法:

 
  • void afterPropertiesSet() throws Exception;

您可以实现以上接口,在 afterPropertiesSet 方法内指定 Bean 初始化后需要执行的操作。

 
  1. <bean id="..." class="..." />
  2. public class User implements InitializingBean {
  3. @Override
  4. public void afterPropertiesSet() throws Exception {
  5. System.out.println("调用接口:InitializingBean,方法:afterPropertiesSet,无参数");
  6. }
  7. }

2. 配置XML

可以通过 init-method 属性指定 Bean 初始化后执行的方法。

 
  1. <bean id="..." class="..." init-method="init"/>
  2. public class User {
  3. public void init() {
  4. System.out.println("调用init-method指定的初始化方法:init" );
  5. }
  6. }

3. 使用注解

使用 @PostConstruct 注解标明该方法为 Bean 初始化后的方法。

 
  1. public class ExampleBean {
  2. @PostConstruct
  3. public void init() {
  4. System.out.println("@PostConstruct注解指定的初始化方法:init" );
  5. }
  6. }

销毁回调

1. 使用接口

org.springframework.beans.factory.DisposableBean 接口提供了以下方法:

 
  • void destroy() throws Exception;

您可以实现以上接口,在 destroy 方法内指定 Bean 初始化后需要执行的操作。

 
  1. <bean id="..." class="..." />
  2. public class User implements InitializingBean {
  3. @Override
  4. public void afterPropertiesSet() throws Exception {
  5. System.out.println("调用接口:InitializingBean,方法:afterPropertiesSet,无参数");
  6. }
  7. }

2. 配置XML

可以通过 destroy-method 属性指定 Bean 销毁后执行的方法。

 
  1. <bean id="..." class="..." destroy-method="destroy"/>
  2. public class User {
  3. public void destroy() {
  4. System.out.println("调用destroy-method指定的销毁方法:destroy" );
  5. }
  6. }

3. 使用注解

使用 @PreDestory 注解标明该方法为 Bean 销毁前执行的方法。

 
  1. public class ExampleBean {
  2. @PreDestory 
  3. public void destroy() {
  4. System.out.println("@PreDestory注解指定的初始化方法:destroy" );
  5. }
  6. }

示例

下面使用 Eclipse IDE 演示如何通过配置 XML 的方式实现初始化回调和销毁回调,步骤如下:

  1. 创建 SpringDemo 项目,并在 src 目录下创建 net.biancheng 包。
  2. 添加相应的 jar 包,可以参考《第一个Spring程序》一节。
  3. 在 net.biancheng 包下创建 HelloWorld 和 MainApp 类。
  4. 在 src 目录下创建 Spring 配置文件 Beans.xml。
  5. 运行 SpringDemo 项目。


HelloWorld 类代码如下。

 
  1. package net.biancheng;
  2. public class HelloWorld {
  3. private String message;
  4. public void setMessage(String message) {
  5. this.message = message;
  6. }
  7. public void getMessage() {
  8. System.out.println("message : " + message);
  9. }
  10. public void init() {
  11. System.out.println("Bean正在进行初始化");
  12. }
  13. public void destroy() {
  14. System.out.println("Bean将要被销毁");
  15. }
  16. }


MainApp 类代码如下,该类中我们使用 AbstractApplicationContext 类的 registerShutdownHook() 方法,来确保正常关机并调用相关的 destroy() 方法。

 
  1. package net.biancheng;
  2. import org.springframework.context.support.AbstractApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class MainApp {
  5. public static void main(String[] args) {
  6. AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
  7. HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
  8. obj.getMessage();
  9. context.registerShutdownHook();
  10. }
  11. }

Beans.xml 配置文件代码如下。

 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  6. <bean id="helloWorld" class="net.biancheng.HelloWorld"
  7. init-method="init" destroy-method="destroy">
  8. <property name="message" value="Hello World!" />
  9. </bean>
  10. </beans>


运行结果如下。

Bean正在进行初始化
message : Hello World!
Bean将要被销毁

默认的初始化和销毁方法

如果多个 Bean 需要使用相同的初始化或者销毁方法,不用为每个 bean 声明初始化和销毁方法,可以使用 default-init-method 和 default-destroy-method 属性,如下所示。

 
  1. <beans xmlns="http://www.springframework.org/schema/beans"
  2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://www.springframework.org/schema/beans
  4. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
  5. default-init-method="init"
  6. default-destroy-method="destroy">
  7. <bean id="..." class="...">
  8. ...
  9. </bean>
  10. </beans>

9.BeanPostProcessor(Spring后置处理器)

Spring Bean生命周期Spring Bean继承 >

本节主要介绍在《Spring Bean生命周期》一节提到的 BeanPostProcessor 接口。

BeanPostProcessor 接口也被称为后置处理器,通过该接口可以自定义调用初始化前后执行的操作方法。

BeanPostProcessor 接口源码如下:

 
  1. public interface BeanPostProcessor {
  2. Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
  3. Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
  4. }

postProcessBeforeInitialization 在 Bean 实例化、依赖注入后,初始化前调用。postProcessAfterInitialization 在 Bean 实例化、依赖注入、初始化都完成后调用。

当需要添加多个后置处理器实现类时,默认情况下 Spring 容器会根据后置处理器的定义顺序来依次调用。也可以通过实现 Ordered 接口的 getOrder 方法指定后置处理器的执行顺序。该方法返回值为整数,默认值为 0,值越大优先级越低。

示例

下面使用 Eclipse IDE 演示 BeanPostProcessor 接口的用法,步骤如下:

  1. 创建 SpringDemo 项目,并在 src 目录下创建 net.biancheng 包。
  2. 添加相应的 jar 包,可以参考《第一个Spring程序》一节。
  3. 在 net.biancheng 包下创建 HelloWorld、InitHelloWorld、InitHelloWorld2 和 MainApp 类。
  4. 在 src 目录下创建 Spring 配置文件 Beans.xml。
  5. 运行 SpringDemo 项目。


HelloWorld 类代码如下。

 
  1. package net.biancheng;
  2. public class HelloWorld {
  3. private String message;
  4. public void setMessage(String message) {
  5. this.message = message;
  6. }
  7. public void getMessage() {
  8. System.out.println("Message : " + message);
  9. }
  10. public void init() {
  11. System.out.println("Bean正在初始化");
  12. }
  13. public void destroy() {
  14. System.out.println("Bean将要被销毁");
  15. }
  16. }

InitHelloWorld 类代码如下。

 
  1. package net.biancheng;
  2. import org.springframework.beans.BeansException;
  3. import org.springframework.beans.factory.config.BeanPostProcessor;
  4. import org.springframework.core.Ordered;
  5. public class InitHelloWorld implements BeanPostProcessor, Ordered {
  6. @Override
  7. public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  8. System.out.println("A Before : " + beanName);
  9. return bean;
  10. }
  11. @Override
  12. public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  13. System.out.println("A After : " + beanName);
  14. return bean;
  15. }
  16. @Override
  17. public int getOrder() {
  18. return 5;
  19. }
  20. }

需要注意的是,postProcessBeforeInitialization 和 postProcessAfterInitialization 方法返回值不能为 null,否则会报空指针异常或者通过 getBean() 方法获取不到 Bean 实例对象,因为后置处理器从Spring IoC 容器中取出 Bean 实例对象后没有再次放回到 IoC 容器中。

InitHelloWorld2 的代码如下。

 
  1. package net.biancheng;
  2. import org.springframework.beans.BeansException;
  3. import org.springframework.beans.factory.config.BeanPostProcessor;
  4. import org.springframework.core.Ordered;
  5. public class InitHelloWorld2 implements BeanPostProcessor, Ordered {
  6. @Override
  7. public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  8. System.out.println("B Before : " + beanName);
  9. return bean;
  10. }
  11. @Override
  12. public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  13. System.out.println("B After : " + beanName);
  14. return bean;
  15. }
  16. @Override
  17. public int getOrder() {
  18. return 0;
  19. }
  20. }

Beans.xml 代码如下。

 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  6. <bean id="helloWorld" class="net.biancheng.HelloWorld"
  7. init-method="init" destroy-method="destroy">
  8. <property name="message" value="Hello World!" />
  9. </bean>
  10. <!-- 注册处理器 -->
  11. <bean class="net.biancheng.InitHelloWorld" />
  12. <bean class="net.biancheng.InitHelloWorld2" />
  13. </beans>

MainApp 类代码如下。

 
  1. package net.biancheng;
  2. import org.springframework.context.support.AbstractApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class MainApp {
  5. public static void main(String[] args) {
  6. AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
  7. HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
  8. obj.getMessage();
  9. context.registerShutdownHook();
  10. }
  11. }

运行结果如下。

B Before : helloWorld
A Before : helloWorld
Bean正在初始化
B After : helloWorld
A After : helloWorld
Message : Hello World!
Bean将要被销毁


由运行结果可以看出,postProcessBeforeInitialization 方法是在 Bean 实例化和依赖注入后,自定义初始化方法前执行的。而 postProcessAfterInitialization 方法是在自定义初始化方法后执行的。由于 getOrder 方法返回值越大,优先级越低,所以 InitHelloWorld2 先执行。

10.Spring Bean继承

Spring后置处理器Spring依赖注入 >

Bean 定义可以包含很多配置信息,包括构造函数参数、属性值和容器的一些具体信息,如初始化方法、销毁方法等。子 Bean 可以继承父 Bean 的配置数据,根据需要,子 Bean 可以重写值或添加其它值。

需要注意的是,Spring Bean 定义的继承与 Java 中的继承无关。您可以将父 Bean 的定义作为一个模板,其它子 Bean 从父 Bean 中继承所需的配置。

在配置文件中通过 parent 属性来指定继承的父 Bean。

示例

下面使用 Eclipse IDE 演示 Bean 继承,步骤如下:

  1. 创建 SpringDemo 项目,并在 src 目录下创建 net.biancheng 包。
  2. 添加相应的 jar 包,可以参考《第一个Spring程序》一节。
  3. 在 net.biancheng 包下创建 HelloWorld、HelloChina 和 MainApp 类。
  4. 在 src 目录下创建 Spring 配置文件 Beans.xml。
  5. 运行 SpringDemo 项目。


HelloWorld 类代码如下。

 
  1. package net.biancheng;
  2. public class HelloWorld {
  3. private String message1;
  4. private String message2;
  5. public void setMessage1(String message) {
  6. this.message1 = message;
  7. }
  8. public void setMessage2(String message) {
  9. this.message2 = message;
  10. }
  11. public void getMessage1() {
  12. System.out.println("World Message1 : " + message1);
  13. }
  14. public void getMessage2() {
  15. System.out.println("World Message2 : " + message2);
  16. }
  17. }

HelloChina 类代码如下。

 
  1. package net.biancheng;
  2. public class HelloChina {
  3. private String message1;
  4. private String message2;
  5. private String message3;
  6. public void setMessage1(String message) {
  7. this.message1 = message;
  8. }
  9. public void setMessage2(String message) {
  10. this.message2 = message;
  11. }
  12. public void setMessage3(String message) {
  13. this.message3 = message;
  14. }
  15. public void getMessage1() {
  16. System.out.println("China Message1 : " + message1);
  17. }
  18. public void getMessage2() {
  19. System.out.println("China Message2 : " + message2);
  20. }
  21. public void getMessage3() {
  22. System.out.println("China Message3 : " + message3);
  23. }
  24. }

在配置文件中,分别为 HelloWorld 中的 message1 和 message2 赋值。使用 parent 属性将 HelloChain 定义为 HelloWorld 的子类,并为 HelloChain 中的 message1 和 message3 赋值。Beans.xml 文件代码如下。

 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  6. <bean id="helloWorld" class="net.biancheng.HelloWorld">
  7. <property name="message1" value="Hello World!" />
  8. <property name="message2" value="Hello World2!" />
  9. </bean>
  10. <bean id="helloChina" class="net.biancheng.HelloChina"
  11. parent="helloWorld">
  12. <property name="message1" value="Hello China!" />
  13. <property name="message3" value="Hello China3!" />
  14. </bean>
  15. </beans>

MainApp 类代码如下。

 
  1. package net.biancheng;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class MainApp {
  5. public static void main(String[] args) {
  6. ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
  7. HelloWorld objA = (HelloWorld) context.getBean("helloWorld");
  8. objA.getMessage1();
  9. objA.getMessage2();
  10. HelloChina objB = (HelloChina) context.getBean("helloChina");
  11. objB.getMessage1();
  12. objB.getMessage2();
  13. objB.getMessage3();
  14. }
  15. }

运行结果如下。

World Message1 : Hello World!
World Message2 : Hello World2!
China Message1 : Hello China!
China Message2 : Hello World2!
China Message3 : Hello China3!

由结果可以看出,我们在创建 helloChina 时并没有给 message2 赋值,但是由于 Bean 的继承,将值传递给了 message2。

Bean定义模板

您可以创建一个 Bean 定义模板,该模板只能被继承,不能被实例化。创建 Bean 定义模板时,不用指定 class 属性,而是指定 abstarct="true" 将该 Bean 定义为抽象 Bean,如下所示。

纯文本复制
 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  6. <bean id="beanTeamplate" abstract="true">
  7. <property name="message1" value="Hello World!" />
  8. <property name="message2" value="Hello World2!" />
  9. <property name="message3" value="Hello World3!" />
  10. </bean>
  11. <bean id="helloChina" class="net.biancheng.HelloChina"
  12. parent="beanTeamplate">
  13. <property name="message1" value="Hello China!" />
  14. <property name="message3" value="Hello China!" />
  15. </bean>
  16. </beans>

11.Spring依赖注入

Spring Bean继承Spring注入内部Bean >

Spring 依赖注入(Dependency Injection,DI)和控制反转含义相同,它们是从两个角度描述的同一个概念。使用依赖注入可以更轻松的管理和测试应用程序。

当某个 Java 实例需要另一个 Java 实例时,传统的方法是由调用者创建被调用者的实例(例如,使用 new 关键字获得被调用者实例),而使用 Spring 框架后,被调用者的实例不再由调用者创建,而是由 Spring 容器创建,这称为控制反转。

Spring 容器在创建被调用者的实例时,会自动将调用者需要的对象实例注入给调用者,调用者通过 Spring 容器获得被调用者实例,这称为依赖注入。

依赖注入主要有两种实现方式,分别是 setter 注入(又称设值注入)和构造函数注入。具体介绍如下。

1)构造函数注入

指 IoC 容器使用构造函数注入被依赖的实例。可以通过调用带参数的构造函数实现依赖注入,每个参数代表一个依赖。

2)setter 注入

指 IoC 容器使用 setter 方法注入被依赖的实例。通过调用无参构造器或无参 static 工厂方法实例化 Bean 后,调用该 Bean 的 setter 方法,即可实现基于 setter 的 DI。

在 Spring 实例化 Bean 的过程中,首先会调用默认的构造方法实例化 Bean 对象,然后通过 Java 的反射机制调用 setXxx() 方法进行属性的注入。因此,setter 注入要求 Bean 的对应类必须满足以下两点要求。

  • 必须提供一个默认的无参构造方法。
  • 必须为需要注入的属性提供对应的 setter 方法。


使用 setter 注入时,在 Spring 配置文件中,需要使用 <bean> 元素的子元素 <property> 为每个属性注入值。而使用构造注入时,在配置文件中,主要使用 <constructor-arg> 标签定义构造方法的参数,使用其 value 属性(或子元素)设置该参数的值。

构造函数注入

下面使用 <constructor-arg> 标签实现构造函数注入。

在 <constructor-arg> 标签中,包含 ref、value、type、index 等属性。value 属性用于注入基本数据类型以及字符串类型的值;ref 属性用于注入已经定义好的 Bean;type 属性用来指定对应的构造函数,当构造函数有多个参数时,可以使用 index 属性指定参数的位置,index 属性值从 0 开始。

例 1

下面使用 Eclipse IDE 演示通过构造函数注入依赖项,步骤如下:

  1. 创建 SpringDemo 项目,并在 src 目录下创建 net.biancheng 包。
  2. 添加相应的 jar 包,可以参考《第一个Spring程序》一节。
  3. 在 net.biancheng 包下创建 Person、Man 和 MainApp 类。
  4. 在 src 目录下创建 Spring 配置文件 Beans.xml。
  5. 运行 SpringDemo 项目。


Person 类代码如下。

 
  1. package net.biancheng;
  2. public class Person {
  3. private Man man;
  4. public Person(Man man) {
  5. System.out.println("在Person的构造函数内");
  6. this.man = man;
  7. }
  8. public void man() {
  9. man.show();
  10. }
  11. }

Man 类代码如下。

 
  1. package net.biancheng;
  2. public class Man {
  3. private String name;
  4. private int age;
  5. public Man() {
  6. System.out.println("在man的构造函数内");
  7. }
  8. public Man(String name, int age) {
  9. System.out.println("在man的有参构造函数内");
  10. this.name = name;
  11. this.age = age;
  12. }
  13. public void show() {
  14. System.out.println("名称:" + name + "\n年龄:" + age);
  15. }
  16. public String getName() {
  17. return name;
  18. }
  19. public void setName(String name) {
  20. this.name = name;
  21. }
  22. public int getAge() {
  23. return age;
  24. }
  25. public void setAge(int age) {
  26. this.age = age;
  27. }
  28. }

Beans.xml 配置文件如下。

 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  6. <bean id="man" class="net.biancheng.Man">
  7. <constructor-arg value="bianchengbang" />
  8. <constructor-arg value="12" type="int" />
  9. </bean>
  10. <bean id="person" class="net.biancheng.Person">
  11. <constructor-arg ref="man" type="java.lang.String"/>
  12. </bean>
  13. </beans>

MainApp 类代码如下。

 
  1. package net.biancheng;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class MainApp {
  5. public static void main(String[] args) {
  6. ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
  7. Person person = (Person) context.getBean("person");
  8. person.man();
  9. }
  10. }

运行结果如下。

在man的有参构造函数内
在Person的构造函数内
名称:bianchengbang
年龄:12

setter注入

下面使用 <property> 标签实现 setter 注入。

在 <property> 标签中,包含 name、ref、value 等属性。name 用于指定参数名称;value 属性用于注入基本数据类型以及字符串类型的值;ref 属性用于注入已经定义好的 Bean。

例 2

在例 1 的基础上修改 Man 类的内容,代码如下。

 
  1. package net.biancheng;
  2. public class Man {
  3. private String name;
  4. private int age;
  5. public Man() {
  6. System.out.println("在man的构造函数内");
  7. }
  8. public Man(String name, int age) {
  9. System.out.println("在man的有参构造函数内");
  10. this.name = name;
  11. this.age = age;
  12. }
  13. public void show() {
  14. System.out.println("名称:" + name + "\n年龄:" + age);
  15. }
  16. public String getName() {
  17. return name;
  18. }
  19. public void setName(String name) {
  20. this.name = name;
  21. }
  22. public int getAge() {
  23. return age;
  24. }
  25. public void setAge(int age) {
  26. this.age = age;
  27. }
  28. }

Beans.xml 配置文件如下。

 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  6. <bean id="person" class="net.biancheng.Person">
  7. <property name="man" ref="man" />
  8. </bean>
  9. <bean id="man" class="net.biancheng.Man">
  10. <property name="name" value="bianchengbang" />
  11. <property name="age" value="12" />
  12. </bean>
  13. </beans>

运行结果如下。

在man的构造函数内
在setMan方法内
名称:bianchengbang
年龄:12

12.Spring注入内部Bean

Spring依赖注入Spring注入集合 >

Java 中在类内部定义的类称为内部类,同理在 Bean 中定义的 Bean 称为内部 Bean。注入内部 Bean 使用 <property> 和 <constructor-arg> 中的 <bean> 标签。如下所示。

 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  6. <bean id="outerBean" class="...">
  7. <property name="target">
  8. <!-- 定义内部Bean -->
  9. <bean class="..." />
  10. </property>
  11. </bean>
  12. </beans>

内部 Bean 的定义不需要指定 id 和 name 。如果指定了,容器也不会将其作为区分 Bean 的标识符,反而会无视内部 Bean 的 scope 属性。所以内部 Bean 总是匿名的,而且总是随着外部 Bean 创建。

在实际开发中很少注入内部 Bean,因为开发者无法将内部的 Bean 注入外部 Bean 以外的其它 Bean。

示例

下面使用 Eclipse IDE 演示如何注入内部 Bean,步骤如下:

  1. 创建 SpringDemo 项目,并在 src 目录下创建 net.biancheng 包。
  2. 添加相应的 jar 包,可以参考《第一个Spring程序》一节。
  3. 在 net.biancheng 包下创建 Person、Man 和 MainApp 类。
  4. 在 src 目录下创建 Spring 配置文件 Beans.xml。
  5. 运行 SpringDemo 项目。


Person 类代码如下。

 
  1. package net.biancheng;
  2. public class Person {
  3. private Man man;
  4. public Man getMan() {
  5. return man;
  6. }
  7. public void setMan(Man man) {
  8. System.out.println("在setMan方法内");
  9. this.man = man;
  10. }
  11. public void man() {
  12. man.show();
  13. }
  14. }

Man 类代码如下。

 
  1. package net.biancheng;
  2. public class Man {
  3. private String name;
  4. private int age;
  5. public Man() {
  6. System.out.println("在man的构造函数内");
  7. }
  8. public Man(String name, int age) {
  9. System.out.println("在man的有参构造函数内");
  10. this.name = name;
  11. this.age = age;
  12. }
  13. public void show() {
  14. System.out.println("名称:" + name + "\n年龄:" + age);
  15. }
  16. public String getName() {
  17. return name;
  18. }
  19. public void setName(String name) {
  20. this.name = name;
  21. }
  22. public int getAge() {
  23. return age;
  24. }
  25. public void setAge(int age) {
  26. this.age = age;
  27. }
  28. }

Beans.xml 配置文件如下。

 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  6. <bean id="person" class="net.biancheng.Person">
  7. <property name="man">
  8. <bean class="net.biancheng.Man">
  9. <property name="name" value="bianchengbang" />
  10. <property name="age" value="12" />
  11. </bean>
  12. </property>
  13. </bean>
  14. </beans>

MainApp 类代码如下。

 
  1. package net.biancheng;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class MainApp {
  5. public static void main(String[] args) {
  6. ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
  7. Person person = (Person) context.getBean("person");
  8. person.man();
  9. }
  10. }

运行结果如下。

在man的有参构造函数内
在Person的构造函数内
名称:bianchengbang
年龄:12

13.Spring注入集合

Spring注入内部BeanSpring Bean自动装配 >

如果需要传递类似于 Java Collection 类型的值,例如 List、Set、Map 和 properties,可以使用 Spring 提供的集合配置标签,如下表所示。
 

标签说明
<list>用于注入 list 类型的值,允许重复
<set>用于注入 set 类型的值,不允许重复
<map>用于注入 key-value 的集合,其中 key-value 可以是任意类型
<props>用于注入 key-value 的集合,其中 key-value 都是字符串类型

示例

下面使用 Eclipse IDE 演示如何注入集合,步骤如下:

  1. 创建 SpringDemo 项目,并在 src 目录下创建 net.biancheng 包。
  2. 添加相应的 jar 包,可以参考《第一个Spring程序》一节。
  3. 在 net.biancheng 包下创建 JavaCollection、Man 和 MainApp 类。
  4. 在 src 目录下创建 Spring 配置文件 Beans.xml。
  5. 运行 SpringDemo 项目。


JavaCollection 类代码如下。

 
  1. package net.biancheng;
  2. import java.util.*;
  3. public class JavaCollection {
  4. List manList;
  5. Set manSet;
  6. Map manMap;
  7. Properties manProp;
  8. public void setManList(List manList) {
  9. this.manList = manList;
  10. }
  11. public List getManList() {
  12. System.out.println("List Elements :" + manList);
  13. return manList;
  14. }
  15. public void setManSet(Set manSet) {
  16. this.manSet = manSet;
  17. }
  18. public Set getManSet() {
  19. System.out.println("Set Elements :" + manSet);
  20. return manSet;
  21. }
  22. public void setManMap(Map manMap) {
  23. this.manMap = manMap;
  24. }
  25. public Map getManMap() {
  26. System.out.println("Map Elements :" + manMap);
  27. return manMap;
  28. }
  29. public void setManProp(Properties manProp) {
  30. this.manProp = manProp;
  31. }
  32. public Properties getManProp() {
  33. System.out.println("Property Elements :" + manProp);
  34. return manProp;
  35. }
  36. }

MainApp 类代码如下。

 
  1. package net.biancheng;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class MainApp {
  5. public static void main(String[] args) {
  6. ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
  7. JavaCollection jc = (JavaCollection) context.getBean("javaCollection");
  8. jc.getManList();
  9. jc.getManSet();
  10. jc.getManMap();
  11. jc.getManProp();
  12. }
  13. }

Beans.xml 配置文件如下。

 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  6. <bean id="javaCollection" class="net.biancheng.JavaCollection">
  7. <property name="manList">
  8. <list>
  9. <value>编程帮</value>
  10. <value>百度</value>
  11. <value>C语言中文网</value>
  12. <value>C语言中文网</value>
  13. </list>
  14. </property>
  15. <property name="manSet">
  16. <set>
  17. <value>编程帮</value>
  18. <value>百度</value>
  19. <value>C语言中文网</value>
  20. <value>C语言中文网</value>
  21. </set>
  22. </property>
  23. <property name="manMap">
  24. <map>
  25. <entry key="1" value="编程帮" />
  26. <entry key="2" value="百度" />
  27. <entry key="3" value="C语言中文网" />
  28. <entry key="4" value="C语言中文网" />
  29. </map>
  30. </property>
  31. <property name="manProp">
  32. <props>
  33. <prop key="one">编程帮</prop>
  34. <prop key="one">编程帮</prop>
  35. <prop key="two">百度</prop>
  36. <prop key="three">C语言中文网</prop>
  37. <prop key="four">C语言中文网</prop>
  38. </props>
  39. </property>
  40. </bean>
  41. </beans>


运行结果如下。

List Elements :[编程帮, 百度, C语言中文网, C语言中文网]
Set Elements :[编程帮, 百度, C语言中文网]
Map Elements :{1=编程帮, 2=百度, 3=C语言中文网, 4=C语言中文网}
Property Elements :{two=百度, one=编程帮, three=C语言中文网, four=C语言中文网}

注入Bean引用

也可以在集合元素中注入 Bean,如下所示。

 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  6. <bean id="..." class="...">
  7. <property name="manList">
  8. <list>
  9. <ref bean="man1" />
  10. <ref bean="man2" />
  11. <value>编程帮</value>
  12. </list>
  13. </property>
  14. <property name="manSet">
  15. <set>
  16. <ref bean="man1" />
  17. <ref bean="man2" />
  18. <value>编程帮</value>
  19. </set>
  20. </property>
  21. <property name="manMap">
  22. <map>
  23. <entry key="one" value="编程帮" />
  24. <entry key="two" value-ref="man1" />
  25. <entry key="three" value-ref="man2" />
  26. </map>
  27. </property>
  28. </bean>
  29. </beans>

注入null和空字符串的值

Spring 会把属性的空参数直接当成空字符串来处理,如果您需要传递一个空字符串值,可以这样写:

 
  • <bean id = "..." class = "exampleBean">
  • <property name = "email" value = ""/>
  • </bean>

等效于以下代码

 
  • exampleBean.setEmail("")


如果需要传递 NULL 值,<null/> 元素用来处理 Null 值。

 
  • <bean id = "..." class = "exampleBean">
  • <property name = "email"><null/></property>
  • </bean>

等效于以下代码

 
  • exampleBean.setEmail(null)

14.Spring Bean自动装配

Spring注入集合Spring基于注解装配Bean >

Bean 的装配可以理解为依赖关系注入,Bean 的装配方式也就是 Bean 的依赖注入方式。Spring 容器支持多种装配 Bean 的方式,如基于 XML 的 Bean 装配、基于 Annotation 的 Bean 装配和自动装配等。

Spring 基于 XML 的装配通常采用两种实现方式,即我们在《Spring依赖注入》一节介绍的 setter 注入和构造注入。本节介绍如何配置自动装配。

自动装配就是指 Spring 容器在不使用 <constructor-arg> 和<property> 标签的情况下,可以自动装配(autowire)相互协作的 Bean 之间的关联关系,将一个 Bean 注入其他 Bean 的 Property 中。

使用自动装配需要配置 <bean> 元素的 autowire 属性。autowire 属性有五个值,具体说明如下表所示。
 

autowire 的属性和作用
名称说明
no默认值,表示不使用自动装配,Bean 依赖必须通过 ref 元素定义。
byName根据 Property 的 name 自动装配,如果一个 Bean 的 name 和另一个 Bean 中的 Property 的 name 相同,则自动装配这个 Bean 到 Property 中。
byType根据 Property 的数据类型(Type)自动装配,如果一个 Bean 的数据类型兼容另一个 Bean 中 Property 的数据类型,则自动装配。
constructor类似于 byType,根据构造方法参数的数据类型,进行 byType 模式的自动装配。
autodetect(3.0版本不支持)如果 Bean 中有默认的构造方法,则用 constructor 模式,否则用 byType 模式。

示例

下面使用 Eclipse IDE 演示 Spring Bean 的自动装配,步骤如下:

  1. 创建 SpringDemo 项目,并在 src 目录下创建 net.biancheng 包。
  2. 添加相应的 jar 包,可以参考《第一个Spring程序》一节。
  3. 在 net.biancheng 包下创建 Person、Man 和 MainApp 类。
  4. 在 src 目录下创建 Spring 配置文件 Beans.xml。
  5. 运行 SpringDemo 项目。


Person 类代码如下。

 
  1. package net.biancheng;
  2. public class Person {
  3. private Man man;
  4. public Person(Man man) {
  5. System.out.println("在Person的构造函数内");
  6. this.man = man;
  7. }
  8. public void man() {
  9. man.show();
  10. }
  11. }

Man 类代码如下。

 
  1. package net.biancheng;
  2. public class Man {
  3. private String name;
  4. private int age;
  5. public Man() {
  6. System.out.println("在man的构造函数内");
  7. }
  8. public Man(String name, int age) {
  9. System.out.println("在man的有参构造函数内");
  10. this.name = name;
  11. this.age = age;
  12. }
  13. public void show() {
  14. System.out.println("名称:" + name + "\n年龄:" + age);
  15. }
  16. public String getName() {
  17. return name;
  18. }
  19. public void setName(String name) {
  20. this.name = name;
  21. }
  22. public int getAge() {
  23. return age;
  24. }
  25. public void setAge(int age) {
  26. this.age = age;
  27. }
  28. }

MainApp 类代码如下。

 
  1. package net.biancheng;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class MainApp {
  5. public static void main(String[] args) {
  6. ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
  7. Person person = (Person) context.getBean("person");
  8. person.man();
  9. }
  10. }

1)不使用自动装配(autowire="no")

autowire="no" 表示不使用自动装配,需要手动注入,Bean 依赖通过 ref 元素定义,Beans.xml 配置文件如下。

 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  6. <bean id="man" class="net.biancheng.Man">
  7. <constructor-arg value="bianchengbang" />
  8. <constructor-arg value="12" type="int" />
  9. </bean>
  10. <bean id="person" class="net.biancheng.Person" autowire="no">
  11. <constructor-arg ref="man" type="java.lang.String"/>
  12. </bean>
  13. </beans>

2)按名称自动装配(autowire="byName")

autowire="byName" 表示按属性名称自动装配,XML 文件中 Bean 的 id 必须与类中的属性名称相同。配置文件内容修改如下。

 
  1. <bean id="man" class="net.biancheng.Man">
  2. <constructor-arg value="bianchengbang" />
  3. <constructor-arg value="12" type="int" />
  4. </bean>
  5. <bean id="person" class="net.biancheng.Person" autowire="byName"/>


运行结果如下。

在man的有参构造函数内
在Person的构造函数内
名称:bianchengbang
年龄:12

如果更改 Bean 的名称,很可能不会注入依赖项。

将 Bean 的名称更改为 man1,配置文件如下:

 
  1. <bean id="man1" class="net.biancheng.Man">
  2. <constructor-arg value="bianchengbang" />
  3. <constructor-arg value="12" type="int" />
  4. </bean>
  5. <bean id="person" class="net.biancheng.Person" autowire="byName"/>

注入失败,异常信息为:

在man的有参构造函数内
Exception in thread "main" java.lang.NullPointerException
at net.biancheng.Person.man(Person.java:16)
at net.biancheng.MainApp.main(MainApp.java:10)

3)按类型自动装配(autowire="byType")

XML 文件中 Bean 的 id 与类中的属性名称可以不同,但必须只有一个类型的 Bean。配置文件内容修改如下。

 
  1. <bean id="man1" class="net.biancheng.Man">
  2. <constructor-arg value="bianchengbang" />
  3. <constructor-arg value="12" type="int" />
  4. </bean>
  5. <bean id="person" class="net.biancheng.Person" autowire="byType"/>


运行结果如下。

在man的有参构造函数内
在Person的构造函数内
名称:bianchengbang
年龄:12

如果您有相同类型的多个 Bean,则注入失败,并且引发异常。

添加 id 为 man2 的 Bean,配置文件代码如下。

 
  1. <bean id="man1" class="net.biancheng.Man">
  2. <constructor-arg value="bianchengbang" />
  3. <constructor-arg value="12" type="int" />
  4. </bean>
  5. <bean id="man2" class="net.biancheng.Man">
  6. <constructor-arg value="bianchengbang" />
  7. <constructor-arg value="12" type="int" />
  8. </bean>
  9. <bean id="person" class="net.biancheng.Person" autowire="byType"/>


异常信息为:

在man的有参构造函数内
在man的有参构造函数内
一月 26, 2021 1:34:14 下午 org.springframework.context.support.AbstractApplicationContext refresh
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'person' defined in class path resource [Beans.xml]: Unsatisfied dependency expressed through bean property 'man'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'net.biancheng.Man' available: expected single matching bean but found 2: man1,man2
...

4)构造函数自动装配(autowire="constructor")

修改 Person 类的代码。

 
  1. package net.biancheng;
  2. public class Person {
  3. private Man man;
  4. public Person(Man man) {
  5. System.out.println("在Person的构造方法内");
  6. this.man = man;
  7. }
  8. public Man getMan() {
  9. return man;
  10. }
  11. public void man() {
  12. man.show();
  13. }
  14. }

类中构造函数的参数必须在配置文件中有相同的类型,配置文件内容修改如下。

 
  1. <bean id="man" class="net.biancheng.Man">
  2. <constructor-arg value="bianchengbang" />
  3. <constructor-arg value="12" type="int" />
  4. </bean>
  5. <bean id="person" class="net.biancheng.Person" autowire="constructor"/>


运行结果如下。

在man的有参构造函数内
在Person的构造函数内
名称:bianchengbang
年龄:12

自动装配的优缺点

优点

  • 自动装配只需要较少的代码就可以实现依赖注入。

缺点

  • 不能自动装配简单数据类型,比如 int、boolean、String 等。
  • 相比较显示装配,自动装配不受程序员控制。

15.Spring基于注解装配Bean

Spring Bean自动装配Spring AOP >

在 Spring 中,尽管可以使用 XML 配置文件实现 Bean 的装配工作,但如果应用中 Bean 的数量较多,会导致 XML 配置文件过于臃肿,从而给维护和升级带来一定的困难。

Java 从 JDK 5.0 以后,提供了 Annotation(注解)功能,Spring 2.5 版本开始也提供了对 Annotation 技术的全面支持,我们可以使用注解来配置依赖注入。

Spring 默认不使用注解装配 Bean,因此需要在配置文件中添加 <context:annotation-config/>,启用注解。

Spring 中常用的注解如下。

1)@Component

可以使用此注解描述 Spring 中的 Bean,但它是一个泛化的概念,仅仅表示一个组件(Bean),并且可以作用在任何层次。使用时只需将该注解标注在相应类上即可。

2)@Repository

用于将数据访问层(DAO层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。

3)@Service

通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。

4)@Controller

通常作用在控制层(如 Struts2 的 Action、SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。

5)@Autowired

可以应用到 Bean 的属性变量、属性的 setter 方法、非 setter 方法及构造函数等,配合对应的注解处理器完成 Bean 的自动配置工作。默认按照 Bean 的类型进行装配。

6)@Resource

作用与 Autowired 相同,区别在于 @Autowired 默认按照 Bean 类型装配,而 @Resource 默认按照 Bean 实例名称进行装配。

@Resource 中有两个重要属性:name 和 type。

Spring 将 name 属性解析为 Bean 的实例名称,type 属性解析为 Bean 的实例类型。如果指定 name 属性,则按实例名称进行装配;如果指定 type 属性,则按 Bean 类型进行装配。如果都不指定,则先按 Bean 实例名称装配,如果不能匹配,则再按照 Bean 类型进行装配;如果都无法匹配,则抛出 NoSuchBeanDefinitionException 异常。

7)@Qualifier

与 @Autowired 注解配合使用,会将默认的按 Bean 类型装配修改为按 Bean 的实例名称装配,Bean 的实例名称由 @Qualifier 注解的参数指定。

示例

下面使用 Eclipse IDE 演示基于注解装配 Bean,步骤如下:

  1. 创建 SpringDemo 项目,并在 src 目录下创建 net.biancheng 包。
  2. 添加相应的 jar 包,可以参考《第一个Spring程序》一节。
  3. 在 net.biancheng 包下创建 UserDao、UserDaoImpl、UserService、UserServiceImpl、UserController 和 MainApp 类。
  4. 在 src 目录下创建 Spring 配置文件 Beans.xml。
  5. 运行 SpringDemo 项目。


UserDao 代码如下。

 
  1. package net.biancheng;
  2. public interface UserDao {
  3. /**
  4. * 输出方法
  5. */
  6. public void outContent();
  7. }

UserDaoImpl 代码如下。

 
  1. package net.biancheng;
  2. import org.springframework.stereotype.Repository;
  3. @Repository("userDao")
  4. public class UserDaoImpl implements UserDao {
  5. @Override
  6. public void outContent() {
  7. System.out.println("编程帮");
  8. }
  9. }

UserService 代码如下。

 
  1. package net.biancheng;
  2. public interface UserService {
  3. /**
  4. * 输出方法
  5. */
  6. public void outContent();
  7. }

UserServiceImpl 代码如下。

 
  1. package net.biancheng;
  2. import javax.annotation.Resource;
  3. import org.springframework.stereotype.Service;
  4. @Service("userService")
  5. public class UserServiceImpl implements UserService{
  6. @Resource(name="userDao")
  7. private UserDao userDao;
  8. public UserDao getUserDao() {
  9. return userDao;
  10. }
  11. public void setUserDao(UserDao userDao) {
  12. this.userDao = userDao;
  13. }
  14. @Override
  15. public void outContent() {
  16. userDao.outContent();
  17. System.out.println("一个在线学习编程的网站");
  18. }
  19. }

UserController 代码如下。

 
  1. package net.biancheng;
  2. import javax.annotation.Resource;
  3. import org.springframework.stereotype.Controller;
  4. @Controller("userController")
  5. public class UserController {
  6. @Resource(name = "userService")
  7. private UserService userService;
  8. public UserService getUserService() {
  9. return userService;
  10. }
  11. public void setUserService(UserService userService) {
  12. this.userService = userService;
  13. }
  14. public void outContent() {
  15. userService.outContent();
  16. System.out.println("专注于分享优质编程教程");
  17. }
  18. }

Beans.xml 代码如下。

 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  7. http://www.springframework.org/schema/context
  8. http://www.springframework.org/schema/context/spring-context.xsd">
  9. <!--使用context命名空间,通知spring扫描指定目录,进行注解的解析 -->
  10. <context:component-scan
  11. base-package="net.biancheng" />
  12. </beans>

MainApp 类代码如下。

 
  1. package net.biancheng;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class MainApp {
  5. public static void main(String[] args) {
  6. ApplicationContext ctx = new ClassPathXmlApplicationContext("Beans.xml");
  7. UserController uc = (UserController) ctx.getBean("userController");
  8. uc.outContent();
  9. }
  10. }

运行结果如下。

编程帮
一个在线学习编程的网站
专注于分享优质编程教程

16.Spring AOP(面向切面编程)

Spring基于注解装配BeanSpring JDK动态代理 >

AOP 的全称是“Aspect Oriented Programming”,即面向切面编程,和 OOP(面向对象编程)类似,也是一种编程思想。

AOP 采取横向抽取机制(动态代理),取代了传统纵向继承机制的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。主要作用是分离功能性需求和非功能性需求,使开发人员可以集中处理某一个关注点或者横切逻辑,减少对业务代码的侵入,增强代码的可读性和可维护性。

简单的说,AOP 的作用就是保证开发者在不修改源代码的前提下,为系统中的业务组件添加某种通用功能。AOP 就是代理模式的典型应用。

目前最流行的 AOP 框架有两个,分别为 Spring AOP 和 AspectJ。

Spring AOP 是基于 AOP 编程模式的一个框架,它能够有效的减少系统间的重复代码,达到松耦合的目的。Spring AOP 使用纯 Java 实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类植入增强的代码。有两种实现方式:基于接口的 JDK 动态代理基于继承的 CGLIB 动态代理

AspectJ 是一个基于 Java 语言的 AOP 框架,从 Spring 2.0 开始,Spring AOP 引入了对 AspectJ 的支持。AspectJ 扩展了 Java 语言,提供了一个专门的编译器,在编译时提供横向代码的植入。

为什么使用AOP

AOP 提供了一种可插入的方式,可以在实际逻辑之前、之后或周围添加其它关注点。比如一个类中有以下 10 个方法。

 
  1. class A{
  2. public void m1(){...}
  3. public void m2(){...}
  4. public void m3(){...}
  5. public void m4(){...}
  6. public void m5(){...}
  7. public void n1(){...}
  8. public void n2(){...}
  9. public void p1(){...}
  10. public void p2(){...}
  11. public void p3(){...}
  12. }

以 m 开头的方法有 5 种,以 n 开头的方法有 2 种,以 p 开头的方法有 3 种。现在要求在以 m 开头的方法后添加发送通知功能。

在不使用 AOP 的情况下,我们必须修改以 m 开头的 5 种方法,在方法中调用发送通知的方法。

如果使用 AOP,我们不用在方法内调用发送通知的方法,只需要在类的方法中定义切入点,然后在 XML 文件中调用。如果需要删除或修改此功能,那么只需要在 XML 文件中进行更改。由此可以看出,使用 AOP 可以增强代码的可维护性。

AOP术语

为了更好地理解 AOP,我们需要了解一些它的相关术语。这些专业术语并不是 Spring 特有的,有些也同样适用于其它 AOP 框架,如 AspectJ。它们的含义如下表所示。
 

名称说明
Joinpoint(连接点)指那些被拦截到的点,在 Spring 中,指可以被动态代理拦截目标类的方法。
Pointcut(切入点)指要对哪些 Joinpoint 进行拦截,即被拦截的连接点。
Advice(通知)指拦截到 Joinpoint 之后要做的事情,即对切入点增强的内容。
Target(目标)指代理的目标对象。
Weaving(植入)指把增强代码应用到目标上,生成代理对象的过程。
Proxy(代理)指生成的代理对象。
Aspect(切面)切入点和通知的结合。


Advice 直译为通知,也有的资料翻译为“增强处理”,共有 5 种类型,如下表所示。
 

通知说明
before(前置通知)通知方法在目标方法调用之前执行
after(后置通知)通知方法在目标方法返回或异常后调用
after-returning(返回后通知)通知方法会在目标方法返回后调用
after-throwing(抛出异常通知)通知方法会在目标方法抛出异常后调用
around(环绕通知)通知方法会将目标方法封装起来


AOP 是 Spring 的核心之一,在 Spring 中经常会使用 AOP 来简化编程。在 Spring 框架中使用 AOP 主要有以下优势。

  • 提供声明式企业服务,特别是作为 EJB 声明式服务的替代品。最重要的是,这种服务是声明式事务管理。
  • 允许用户实现自定义切面。在某些不适合用 OOP 编程的场景中,采用 AOP 来补充。
  • 可以对业务逻辑的各个部分进行隔离,从而使业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时也提高了开发效率。

17.Spring JDK动态代理

Spring AOPSpring CGLlB动态代理 >

Spring JDK 动态代理需要实现 InvocationHandler 接口,重写 invoke 方法,客户端使用 Java.lang.reflect.Proxy 类产生动态代理类的对象。

示例

下面使用 Eclipse IDE 演示 JDK 动态代理,步骤如下:

  • 创建 SpringDemo 项目,并在 src 目录下创建 net.biancheng 包。
  • 在 net.biancheng 包下创建 UserManager(用户管理接口)、UserManagerImpl(用户管理接口实现类)、MyAspect(切面类)和 JdkProxy(动态代理类)。
  • 运行 SpringDemo 项目。


UserManager 类代码如下。

 
  1. package net.biancheng;
  2. public interface UserManager {
  3. // 新增用户抽象方法
  4. void addUser(String userName, String password);
  5. // 删除用户抽象方法
  6. void delUser(String userName);
  7. }

UserManagerImpl 类代码如下。

 
  1. package net.biancheng;
  2. public class UserManagerImpl implements UserManager {
  3. @Override
  4. public void addUser(String userName, String password) {
  5. System.out.println("正在执行添加用户方法");
  6. System.out.println("用户名称: " + userName + " 密码: " + password);
  7. }
  8. @Override
  9. public void delUser(String userName) {
  10. System.out.println("正在执行删除用户方法");
  11. System.out.println("用户名称: " + userName);
  12. }
  13. }

MyAspect 类代码如下。

 
  1. package net.biancheng;
  2. public class MyAspect {
  3. public void myBefore() {
  4. System.out.println("方法执行之前");
  5. }
  6. public void myAfter() {
  7. System.out.println("方法执行之后");
  8. }
  9. }

MyAspect 类在切面中定义了两个增强的方法,分别为 myBefore 和 myAfter。

JdkProxy 类代码如下。

 
  1. package net.biancheng;
  2. import java.lang.reflect.InvocationHandler;
  3. import java.lang.reflect.Method;
  4. import java.lang.reflect.Proxy;
  5. /**
  6. * JDK动态代理实现InvocationHandler接口
  7. *
  8. * @author 编程帮
  9. *
  10. */
  11. public class JdkProxy implements InvocationHandler {
  12. private Object target; // 需要代理的目标对象
  13. final MyAspect myAspect = new MyAspect();
  14. @Override
  15. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  16. myAspect.myBefore();
  17. Object result = method.invoke(target, args);
  18. myAspect.myAfter();
  19. return result;
  20. }
  21. // 定义获取代理对象方法
  22. private Object getJDKProxy(Object targetObject) {
  23. // 为目标对象target赋值
  24. this.target = targetObject;
  25. // JDK动态代理只能代理实现了接口的类,从 newProxyInstance 函数所需的参数就可以看出来
  26. return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(),
  27. this);
  28. }
  29. public static void main(String[] args) {
  30. JdkProxy jdkProxy = new JdkProxy(); // 实例化JDKProxy对象
  31. UserManager user = (UserManager) jdkProxy.getJDKProxy(new UserManagerImpl()); // 获取代理对象
  32. user.addUser("bianchengbang", "www.biancheng.net"); // 执行新增方法
  33. user.delUser("bianchengbang");    // 执行删除方法
  34. }
  35. }

运行结果如下。

方法执行之前
正在执行添加用户方法
用户名称: bianchengbang 密码: www.biancheng.net
方法执行之后
方法执行之前
正在执行删除用户方法
用户名称: bianchengbang
方法执行之后

18.Spring CGLlB动态代理

Spring JDK动态代理Spring集成AspectJ >

通过学习《Spring JDK动态代理》一节可以了解到,JDK 动态代理使用起来非常简单,但是 JDK 动态代理的目标类必须要实现一个或多个接口,具有一定的局限性。如果不希望实现接口,可以使用 CGLIB代理。

CGLIB(Code Generation Library)是一个高性能开源的代码生成包,它被许多 AOP 框架所使用,其底层是通过使用一个小而快的字节码处理框架 ASM(Java 字节码操控框架)转换字节码并生成新的类。使用 CGLIB 需要导入 CGLIB 和 ASM 包,即 asm-x.x.jar 和 CGLIB-x.x.x.jar 。如果您已经导入了 Spring 的核心包 spring-core-x.x.x.RELEASE.jar,就不用再导入 asm-x.x.jar 和 cglib-x.x.x.jar 了。

Spring 核心包中包含 CGLIB 和 asm,也就是说 Spring 核心包已经集成了 CGLIB 所需要的包,所以在开发中不需要另外导入asm-x.x.jar 和 cglib-x.x.x.jar 包了。

示例

下面使用 Eclipse IDE 演示 CGLIB 动态代理的使用,步骤如下:

  • 创建 SpringDemo 项目,并在 src 目录下创建 net.biancheng 包。
  • 导入相关 JAR 包。
  • 在 net.biancheng 包下创建 UserManager(用户管理接口)、UserManagerImpl(用户管理接口实现类)、MyAspect(切面类)和 CGLIBProxy(动态代理类)。
  • 运行 SpringDemo 项目。


UserManager 类代码如下。

 
  1. package net.biancheng;
  2. public interface UserManager {
  3. // 新增用户抽象方法
  4. void addUser(String userName, String password);
  5. // 删除用户抽象方法
  6. void delUser(String userName);
  7. }

UserManagerImpl 类代码如下。

 
  1. package net.biancheng;
  2. public class UserManagerImpl implements UserManager {
  3. @Override
  4. public void addUser(String userName, String password) {
  5. System.out.println("正在执行添加用户方法");
  6. System.out.println("用户名称: " + userName + " 密码: " + password);
  7. }
  8. @Override
  9. public void delUser(String userName) {
  10. System.out.println("正在执行删除用户方法");
  11. System.out.println("用户名称: " + userName);
  12. }
  13. }

MyAspect 类代码如下。

 
  1. package net.biancheng;
  2. public class MyAspect {
  3. public void myBefore() {
  4. System.out.println("方法执行之前");
  5. }
  6. public void myAfter() {
  7. System.out.println("方法执行之后");
  8. }
  9. }

CglibProxy 类代码如下。

 
  1. package net.biancheng;
  2. import java.lang.reflect.Method;
  3. import org.springframework.CGLIB.proxy.Enhancer;
  4. import org.springframework.CGLIB.proxy.MethodInterceptor;
  5. import org.springframework.CGLIB.proxy.MethodProxy;
  6. /**
  7. * CGLIB动态代理,实现MethodInterceptor接口
  8. *
  9. * @author 编程帮
  10. *
  11. */
  12. public class CglibProxy implements MethodInterceptor {
  13. private Object target;// 需要代理的目标对象
  14. final MyAspect myAspect = new MyAspect();
  15. // 重写拦截方法
  16. @Override
  17. public Object intercept(Object obj, Method method, Object[] arr, MethodProxy proxy) throws Throwable {
  18. myAspect.myBefore();
  19. Object invoke = method.invoke(target, arr);// 方法执行,参数:target目标对象 arr参数数组
  20. myAspect.myAfter();
  21. return invoke;
  22. }
  23. // 定义获取代理对象方法
  24. public Object getCglibProxy(Object objectTarget) {
  25. // 为目标对象target赋值
  26. this.target = objectTarget;
  27. Enhancer enhancer = new Enhancer();
  28. // 设置父类,因为CGLIB是针对指定的类生成一个子类,所以需要指定父类
  29. enhancer.setSuperclass(objectTarget.getClass());
  30. enhancer.setCallback(this);// 设置回调
  31. Object result = enhancer.create();// 创建并返回代理对象
  32. return result;
  33. }
  34. public static void main(String[] args) {
  35. CglibProxy cglib= new CglibProxy();// 实例化CglibBProxy对象
  36. UserManager user = (UserManager) cglib.getCglibProxy(new UserManagerImpl());// 获取代理对象
  37. user.addUser("bianchengbang", "www.biancheng.net"); // 执行新增方法
  38. user.delUser("bianchengbang"); // 执行删除方法
  39. }
  40. }

运行结果如下。

方法执行之前
正在执行添加用户方法
用户名称: bianchengbang 密码: www.biancheng.net
方法执行之后
方法执行之前
正在执行删除用户方法
用户名称: bianchengbang
方法执行之后

JDK代理和CGLIB代理的区别

JDK 动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用 InvokeHandler 来处理。而 CGLIB 动态代理是利用 ASM 开源包,加载代理对象类的 class 文件,通过修改其字节码生成子类来处理。

JDK 动态代理只能对实现了接口的类生成代理,而不能针对类。

CGLIB 是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。因为是继承,所以该类或方法不能声明成 final 类型。

JDK动态代理特点

  • 代理对象必须实现一个或多个接口
  • 以接口的形式接收代理实例,而不是代理类

CGLIB动态代理特点

  • 代理对象不能被 final 修饰
  • 以类或接口形式接收代理实例

JDK与CGLIB动态代理的性能比较

生成代理实例性能:JDK > CGLIB
代理实例运行性能:JDK > CGLIB

19.Spring集成AspectJ

Spring CGLlB动态代理AspectJ基于XML开发AOP >

在《Spring JDK动态代理》和《Spring CGLlB动态代理》一节我们学习了基于代理类的 AOP 实现,Spring 2.0 以后,Spring 新增了对 AspectJ 的支持。在新版本的 Spring 框架中,建议使用 AspectJ 方式开发 AOP。

AspectJ 是一个基于 Java 语言的 AOP 框架,它扩展了 Java 语言,提供了强大的 AOP 功能。

使用 AspectJ 需要导入以下 jar 包:

  • Aspectjrt.jar
  • Aspectjweaver.jar
  • Aspectj.jar


jar 包下载地址:AspectJ Downloads | The Eclipse Foundation

使用 AspectJ 开发 AOP 通常有以下 2 种方式:

AspectJ包下载缓慢解决方法

打开 AspectJ 包下载页面,选择相应的版本,这里我们下载的为 1.9.5 稳定版本。
 


点击 aspectj-1.9.5.jar 进入下载页面,选择 Select another mirror,如下图。
 


根据自己所处地区选择下载,这里我们选择的是中国科学技术大学的下载地址。
 


下载完成后,解压该文件,需要导入的 jar 包在 files 文件夹的 lib 目录下。

20.Spring AOP:基于AspectJ XML开发

Spring集成AspectJAspectJ基于注解开发AOP >

基于 XML 的声明式是指通过 Spring 配置文件的方式来定义切面、切入点及通知,而所有的切面和通知都必须定义在 <aop:config> 元素中。

在使用 <aop:config> 元素之前,我们需要先导入 Spring aop 命名空间,如下所示。

 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:aop="http://www.springframework.org/schema/aop"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  7. http://www.springframework.org/schema/aop
  8. http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
  9. ...
  10. </beans>

定义切面<aop:aspect>

在 Spring 配置文件中,使用 <aop:aspect> 元素定义切面,该元素可以将定义好的 Bean 转换为切面 Bean,所以使用 <aop:aspect> 之前需要先定义一个普通的 Spring Bean。

 
  • <aop:config>
  • <aop:aspect id="myAspect" ref="aBean">
  • ...
  • </aop:aspect>
  • </aop:config>

其中,id 用来定义该切面的唯一表示名称,ref 用于引用普通的 Spring Bean。

定义切入点<aop:pointcut>

<aop:pointcut> 用来定义一个切入点,当 <aop:pointcut>元素作为 <aop:config> 元素的子元素定义时,表示该切入点是全局切入点,它可被多个切面所共享;当 <aop:pointcut> 元素作为 <aop:aspect> 元素的子元素时,表示该切入点只对当前切面有效。

 
  • <aop:config>
  • <aop:pointcut id="myPointCut"
  • expression="execution(* net.biancheng.service.*.*(..))"/>
  • </aop:config>

其中,id 用于指定切入点的唯一标识名称,execution 用于指定切入点关联的切入点表达式。

execution 格式为:

execution(modifiers-pattern returning-type-pattern declaring-type-pattern name-pattern(param-pattern)throws-pattern)

其中:

  • returning-type-pattern、name-pattern、param-pattern 是必须的,其它参数为可选项。
  • modifiers-pattern:指定修饰符,如 private、public。
  • returning-type-pattern:指定返回值类型,*表示可以为任何返回值。如果返回值为对象,则需指定全路径的类名。
  • declaring-type-pattern:指定方法的包名。
  • name-pattern:指定方法名,*代表所有,set* 代表以 set 开头的所有方法。
  • param-pattern:指定方法参数(声明的类型),(..)代表所有参数,(*)代表一个参数,(*,String)代表第一个参数可以为任何值,第二个为 String 类型的值。
  • throws-pattern:指定抛出的异常类型。


例如:execution(* net.biancheng.*.*(..))表示匹配 net.biancheng 包中任意类的任意方法。

定义通知

AspectJ 支持 5 种类型的 advice,如下。

 
  1. <aop:aspect id="myAspect" ref="aBean">
  2. <!-- 前置通知 -->
  3. <aop:before pointcut-ref="myPointCut" method="..."/>
  4. <!-- 后置通知 -->
  5. <aop:after-returning pointcut-ref="myPointCut" method="..."/>
  6. <!-- 环绕通知 -->
  7. <aop:around pointcut-ref="myPointCut" method="..."/>
  8. <!-- 异常通知 -->
  9. <aop:after-throwing pointcut-ref="myPointCut" method="..."/>
  10. <!-- 最终通知 -->
  11. <aop:after pointcut-ref="myPointCut" method="..."/>
  12. ....
  13. </aop:aspect>

示例

下面使用 Eclipse IDE 演示 AspectJ 基于 XML 开发 AOP,步骤如下:

  1. 创建 SpringDemo 项目,并在 src 目录下创建 net.biancheng 包。
  2. 导入 Spring 相关 JAR 包及 Aspectjrt.jar、Aspectjweaver.jar、Aspectj.jar。
  3. 在 net.biancheng 包下创建 Logging、Man、Beans.xml 和 MainApp。
  4. 运行 SpringDemo 项目。


Logging 类的代码如下,定义了在各个点要调用的方法。

 
  1. package net.biancheng;
  2. public class Logging {
  3. /**
  4. * 前置通知
  5. */
  6. public void beforeAdvice() {
  7. System.out.println("前置通知");
  8. }
  9. /**
  10. * 后置通知
  11. */
  12. public void afterAdvice() {
  13. System.out.println("后置通知");
  14. }
  15. /**
  16. * 返回后通知
  17. */
  18. public void afterReturningAdvice(Object retVal) {
  19. System.out.println("返回值为:" + retVal.toString());
  20. }
  21. /**
  22. * 抛出异常通知
  23. */
  24. public void afterThrowingAdvice(IllegalArgumentException ex) {
  25. System.out.println("这里的异常为:" + ex.toString());
  26. }
  27. }

Man 类的代码如下。

 
  1. package net.biancheng;
  2. public class Man {
  3. private String name;
  4. private int age;
  5. public String getName() {
  6. return name;
  7. }
  8. public void setName(String name) {
  9. this.name = name;
  10. }
  11. public int getAge() {
  12. return age;
  13. }
  14. public void setAge(int age) {
  15. this.age = age;
  16. }
  17. public void throwException() {
  18. System.out.println("抛出异常");
  19. throw new IllegalArgumentException();
  20. }
  21. }

Beans.xml 代码如下。

 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:aop="http://www.springframework.org/schema/aop"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  7. http://www.springframework.org/schema/aop
  8. http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
  9. <aop:config>
  10. <aop:aspect id="log" ref="logging">
  11. <aop:pointcut id="selectAll"
  12. expression="execution(* net.biancheng.*.*(..))" />
  13. <aop:before pointcut-ref="selectAll" method="beforeAdvice" />
  14. <aop:after pointcut-ref="selectAll" method="afterAdvice" />
  15. <aop:after-returning pointcut-ref="selectAll"
  16. returning="retVal" method="afterReturningAdvice" />
  17. <aop:after-throwing pointcut-ref="selectAll"
  18. throwing="ex" method="afterThrowingAdvice" />
  19. </aop:aspect>
  20. </aop:config>
  21. <bean id="man" class="net.biancheng.Man">
  22. <property name="name" value="bianchengbang" />
  23. <property name="age" value="12" />
  24. </bean>
  25. <bean id="logging" class="net.biancheng.Logging" />
  26. </beans>

MainApp 类代码如下。

 
  1. package net.biancheng;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class MainApp {
  5. public static void main(String[] args) {
  6. ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
  7. Man man = (Man) context.getBean("man");
  8. man.getName();
  9. man.getAge();
  10. man.throwException();
  11. }
  12. }

运行结果如下。

前置通知
后置通知
返回值为:bianchengbang
前置通知
后置通知
返回值为:12
前置通知
抛出异常
后置通知
这里的异常为:java.lang.IllegalArgumentException
Exception in thread "main" java.lang.IllegalArgumentException

21.Spring AOP:基于AspectJ注解开发

AspectJ基于XML开发AOPSpring JdbcTemplate类 >

在 Spring 中,尽管使用 XML 配置文件可以实现 AOP 开发,但是如果所有的相关配置都集中在配置文件中,势必会导致 XML 配置文件过于臃肿,从而给维护和升级带来一定的困难。

为此,AspectJ 框架为 AOP 开发提供了一套注解。AspectJ 允许使用注解定义切面、切入点和增强处理,Spring 框架可以根据这些注解生成 AOP 代理。

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

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


启用 @AspectJ 注解有以下两种方法:

1)使用@Configuration和@EnableAspectJAutoProxy注解

 
  1. @Configuration
  2. @EnableAspectJAutoProxy
  3. public class Appconfig {
  4. }

2)基于XML配置

在 XML 文件中添加以下内容启用 @AspectJ。

<aop:aspectj-autoproxy>

定义切面@Aspect

AspectJ 类和其它普通的 Bean 一样,可以有方法和字段,不同的是 AspectJ 类需要使用 @Aspect 注解,如下所示。

 
  1. package net.biancheng;
  2. import org.aspectj.lang.annotation.Aspect;
  3. @Aspect
  4. public class AspectModule {
  5. }

AspectJ 类也可以像其它 Bean 一样在 XML 中配置,如下。

 
  1. <bean id = "myAspect" class = "net.biancheng.AspectModule">
  2. ...
  3. </bean>

定义切入点@Pointcut

@Pointcut 注解用来定义一个切入点,如下。

 
  1. // 要求:方法必须是private,返回值类型为void,名称自定义,没有参数
  2. @Pointcut("execution(*net.biancheng..*.*(..))")
  3. private void myPointCut() {
  4. }

相当于以下代码

<aop:pointcut expression="execution(*net.biancheng..*.*(..))"  id="myPointCut"/>

关于 execution 中表达式的使用说明,我们在《AspectJ基于XML开发AOP》一节介绍。

定义通知advice

@AspectJ 支持 5 种类型的 advice,以下为使用 @Before 的示例。

 
  1. @Before("myPointCut()")
  2. public void beforeAdvice(){
  3. ...
  4. }

示例

下面使用 Eclipse IDE 演示 AspectJ 基于注解开发 AOP,步骤如下:

  1. 创建 SpringDemo 项目,并在 src 目录下创建 net.biancheng 包。
  2. 导入 Spring 相关 JAR 包及 Aspectjrt.jar、Aspectjweaver.jar、Aspectj.jar。
  3. 在 net.biancheng 包下创建 Logging、Man、Beans.xml 和 MainApp。
  4. 运行 SpringDemo 项目。


Logging 类代码如下。

 
  1. package net.biancheng;
  2. import org.aspectj.lang.annotation.After;
  3. import org.aspectj.lang.annotation.AfterReturning;
  4. import org.aspectj.lang.annotation.AfterThrowing;
  5. import org.aspectj.lang.annotation.Aspect;
  6. import org.aspectj.lang.annotation.Before;
  7. import org.aspectj.lang.annotation.Pointcut;
  8. @Aspect
  9. public class Logging {
  10. /**
  11. * 定义切入点
  12. */
  13. @Pointcut("execution(* net.biancheng.*.*(..))")
  14. private void selectAll() {
  15. }
  16. /**
  17. * 前置通知
  18. */
  19. @Before("selectAll()")
  20. public void beforeAdvice() {
  21. System.out.println("前置通知");
  22. }
  23. /**
  24. * 后置通知
  25. */
  26. @After("selectAll()")
  27. public void afterAdvice() {
  28. System.out.println("后置通知");
  29. }
  30. /**
  31. * 返回后通知
  32. */
  33. @AfterReturning(pointcut = "selectAll()", returning = "retVal")
  34. public void afterReturningAdvice(Object retVal) {
  35. System.out.println("返回值为:" + retVal.toString());
  36. }
  37. /**
  38. * 抛出异常通知
  39. */
  40. @AfterThrowing(pointcut = "selectAll()", throwing = "ex")
  41. public void afterThrowingAdvice(IllegalArgumentException ex) {
  42. System.out.println("这里的异常为:" + ex.toString());
  43. }
  44. }

Man 类代码如下。

 
  1. package net.biancheng;
  2. public class Man {
  3. private String name;
  4. private int age;
  5. public String getName() {
  6. return name;
  7. }
  8. public void setName(String name) {
  9. this.name = name;
  10. }
  11. public int getAge() {
  12. return age;
  13. }
  14. public void setAge(int age) {
  15. this.age = age;
  16. }
  17. public void throwException() {
  18. System.out.println("抛出异常");
  19. throw new IllegalArgumentException();
  20. }
  21. }

Beans.xml 代码如下。

 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:aop="http://www.springframework.org/schema/aop"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  7. http://www.springframework.org/schema/aop
  8. http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
  9. <aop:aspectj-autoproxy />
  10. <bean id="man" class="net.biancheng.Man">
  11. <property name="name" value="bianchengbang" />
  12. <property name="age" value="12" />
  13. </bean>
  14. <bean id="logging" class="net.biancheng.Logging" />
  15. </beans>

MainApp 类代码如下。

 
  1. package net.biancheng;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class MainApp {
  5. public static void main(String[] args) {
  6. ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
  7. Man man = (Man) context.getBean("man");
  8. man.getName();
  9. man.getAge();
  10. man.throwException();
  11. }
  12. }

运行结果如下。

前置通知
后置通知
返回值为:bianchengbang
前置通知
后置通知
返回值为:12
前置通知
抛出异常
后置通知
这里的异常为:java.lang.IllegalArgumentException

22.Spring JdbcTemplate类

AspectJ基于注解开发AOPSpring集成Log4J >

Spring 针对数据库开发提供了 JdbcTemplate 类,该类封装了 JDBC,支持对数据库的所有操作。

JdbcTemplate 位于 spring-jdbc-x.x.x.jar 包中,其全限定命名为 org.springframework.jdbc.core.JdbcTemplate。此外使用 JdbcTemplate 还需要导入 spring-tx-x.x.x.jar 包,该包用来处理事务和异常。

在 Spring 中,JDBC 的相关信息在配置文件中完成,其配置模板如下所示。

 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http:/www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans.xsd">
  6. <!-- 配置数据源 -->
  7. <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
  8. <!--数据库驱动-->
  9. <property name="driverClassName" value="com.mysql.jdbc.Driver" />
  10. <!--连接数据库的url-->
  11. <property name= "url" value="jdbc:mysql://localhost/xx" />
  12. <!--连接数据库的用户名-->
  13. <property name="username" value="root" />
  14. <!--连接数据库的密码-->
  15. <property name="password" value="root" />
  16. </bean>
  17. <!--配置JDBC模板-->
  18. <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
  19. <!--默认必须使用数据源-->
  20. <property name="dataSource" ref="dataSource"/>
  21. </bean>
  22. <!--配置注入类-->
  23. <bean id="xxx" class="xxx">
  24. <property name="jdbcTemplate" ref="jdbcTemplate"/>
  25. </bean>
  26. ...
  27. </beans>

本节使用 MySQL 数据库,如果您使用的是其它数据库,需要对内容进行相应的修改。

上述代码中定义了 3 个 Bean,分别是 dataSource、jdbcTemplate 和需要注入类的 Bean。其中 dataSource 对应的是 DriverManagerDataSource 类,用于对数据源进行配置;jdbcTemplate 对应 JdbcTemplate 类,该类中定义了 JdbcTemplate 的相关配置。

在 dataSource 中,定义了 4 个连接数据库的属性,如下表所示。
 

属性名说明
driverClassName所使用的驱动名称,对应驱动 JAR 包中的 Driver 类
url数据源所在地址
username访问数据库的用户名
password访问数据库的密码


上表中的属性值需要根据数据库类型或者机器配置的不同进行相应设置。如果数据库类型不同,则需要更改驱动名称。如果数据库不在本地,则需要将 localhost 替换成相应的主机 IP。

在定义 JdbcTemplate 时,需要将 dataSource 注入到 JdbcTemplate 中。而在其他的类中要使用 JdbcTemplate,也需要将 JdbcTemplate 注入到使用类中(通常注入 dao 类中)。

在 JdbcTemplate 类中,提供了大量的查询和更新数据库的方法,如 query()、update() 等,如下表所示。
 

方法说明
public int update(String sql)用于执行新增、修改、删除等语句
args 表示需要传入到 query 中的参数
public int update(String sql,Object... args)
public void execute(String sql)可以执行任意 SQL,一般用于执行 DDL 语句
action 表示执行完 SQL 语句后,要调用的函数
public T execute(String sql, PreparedStatementCallback action)
public T query(String sql, ResultSetExtractor rse)用于执行查询语句
以 ResultSetExtractor 作为参数的 query 方法返回值为 Object,使用查询结果需要对其进行强制转型
以 RowMapper 作为参数的 query 方法返回值为 List
public List query(String sql, RowMapper rse)

示例

下面通过实例演示使用 JdbcTemplate 类操作数据库,步骤如下:

  1. 创建 SpringDemo 项目,并在 src 目录下创建 net.biancheng 包。
  2. 导入 Spring 相关 JAR 包及 mysql-connector-java.x.x.x.jar 包。
  3. 在 net.biancheng 包下创建 User、UserDao、UserDaoImpl、Beans.xml 和 MainApp。
  4. 运行 SpringDemo 项目。


User 类代码如下。

 
  1. package net.biancheng;
  2. public class User {
  3. private int id;
  4. private String name;
  5. private int age;
  6. public User() {
  7. }
  8. public User(String name, Integer age) {
  9. this.name = name;
  10. this.age = age;
  11. }
  12. // 省略set和get方法
  13. }

UserDao 代码如下。

 
  1. package net.biancheng;
  2. import java.util.List;
  3. public interface UserDao {
  4. /**
  5. * 初始化User表
  6. */
  7. void createUserTable();
  8. /**
  9. * 保存用户
  10. */
  11. void saveUser(User user);
  12. /**
  13. * 查询用户
  14. */
  15. List<User> listUser();
  16. }

UserDaoImpl 代码如下。

 
  1. package net.biancheng;
  2. import java.sql.ResultSet;
  3. import java.sql.SQLException;
  4. import java.util.List;
  5. import javax.sql.DataSource;
  6. import org.springframework.jdbc.core.JdbcTemplate;
  7. import org.springframework.jdbc.core.RowMapper;
  8. public class UserDaoImpl implements UserDao {
  9. private JdbcTemplate jdbcTemplate;
  10. private UserDao userDao;
  11. public JdbcTemplate getJdbcTemplate() {
  12. return jdbcTemplate;
  13. }
  14. public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
  15. this.jdbcTemplate = jdbcTemplate;
  16. }
  17. public UserDao getUserDao() {
  18. return userDao;
  19. }
  20. public void setUserDao(UserDao userDao) {
  21. this.userDao = userDao;
  22. }
  23. public void setDataSource(DataSource datasource) {
  24. this.jdbcTemplate = new JdbcTemplate(datasource);
  25. }
  26. @Override
  27. public void createUserTable() {
  28. this.jdbcTemplate.execute("CREATE TABLE `user` (\r\n" + " `id` int(11) NOT NULL AUTO_INCREMENT,\r\n"
  29. + " `name` varchar(50) DEFAULT NULL,\r\n" + " `age` int(11) DEFAULT NULL,\r\n"
  30. + " PRIMARY KEY (`id`)\r\n" + ") ENGINE=MyISAM DEFAULT CHARSET=utf8;");
  31. }
  32. @Override
  33. public void saveUser(User user) {
  34. this.jdbcTemplate.update("INSERT INTO USER(NAME,age) VALUES (?,?)", user.getName(), user.getAge());
  35. }
  36. @Override
  37. public List<User> listUser() {
  38. List<User> users = this.jdbcTemplate.query("SELECT NAME,age FROM USER", new RowMapper<User>() {
  39. public User mapRow(ResultSet rs, int rowNum) throws SQLException {
  40. User user = new User();
  41. user.setName(rs.getString("name"));
  42. user.setAge(rs.getInt("age"));
  43. return user;
  44. }
  45. });
  46. return users;
  47. }
  48. }

Beans.xml 代码如下。

 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xmlns:aop="http://www.springframework.org/schema/aop"
  6. xsi:schemaLocation="http://www.springframework.org/schema/beans
  7. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  8. http://www.springframework.org/schema/context
  9. http://www.springframework.org/schema/context/spring-context.xsd
  10. http://www.springframework.org/schema/aop
  11. http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
  12. <!-- 配置数据源 -->
  13. <bean id="dataSource"
  14. class="org.springframework.jdbc.datasource.DriverManagerDataSource">
  15. <!--数据库驱动 -->
  16. <property name="driverClassName"
  17. value="com.mysql.jdbc.Driver" />
  18. <!--连接数据库的url -->
  19. <property name="url" value="jdbc:mysql://localhost/test" />
  20. <!--连接数据库的用户名 -->
  21. <property name="username" value="root" />
  22. <!--连接数据库的密码 -->
  23. <property name="password" value="root" />
  24. </bean>
  25. <!--配置JDBC模板 -->
  26. <bean id="jdbcTemplate"
  27. class="org.springframework.jdbc.core.JdbcTemplate">
  28. <!--默认必须使用数据源 -->
  29. <property name="dataSource" ref="dataSource" />
  30. </bean>
  31. <bean id="userdao" class="net.biancheng.UserDaoImpl">
  32. <property name="jdbcTemplate" ref="jdbcTemplate" />
  33. </bean>
  34. </beans>

MainApp 类代码如下。

 
  1. package net.biancheng;
  2. import java.util.List;
  3. import org.springframework.context.ApplicationContext;
  4. import org.springframework.context.support.ClassPathXmlApplicationContext;
  5. public class MainApp {
  6. public static void main(String[] args) {
  7. ApplicationContext ctx = new ClassPathXmlApplicationContext("Beans.xml");
  8. UserDao dao = (UserDao) ctx.getBean("userdao");
  9. dao.createUserTable();
  10. dao.saveUser(new User("bianchengbang", 12));
  11. dao.saveUser(new User("baidu", 18));
  12. List<User> users = dao.listUser();
  13. for (User user : users) {
  14. System.out.println("姓名:" + user.getName() + "\t年龄:" + user.getAge());
  15. }
  16. }
  17. }

运行结果如下。

姓名:bianchengbang 年龄:12
姓名:baidu 年龄:18

23.Spring集成Log4J

Spring JdbcTemplate类Spring事务 >

日志是应用软件中不可缺少的部分,Apache 的开源项目 Log4J 是一个功能强大的日志组件。在 Spring 中使用 Log4J 是非常容易的,下面通过例子演示 Log4J 和 Spring 的集成。

使用 Log4J 之前,需要先导入 log4j-x.y.z.jar 包,Log4J 下载地址:https://logging.apache.org/log4j

示例

下面使用 Eclipse IDE 演示 Log4J 的使用,步骤如下:

  1. 创建 SpringDemo 项目,并在 src 目录下创建 net.biancheng 包。
  2. 导入 Spring 相关 JAR 包及 log4j-x.y.z.jar。
  3. 在 net.biancheng 包下创建 HelloWorld、MainApp、Beans.xml 和 log4j.properties。
  4. 运行 SpringDemo 项目。


HelloWorld 类代码如下。

 
  1. package net.biancheng;
  2. public class HelloWorld {
  3. private String message;
  4. public void setMessage(String message) {
  5. this.message = message;
  6. }
  7. public void getMessage() {
  8. System.out.println("消息:" + message);
  9. }
  10. }

MainApp 类代码如下。

 
  1. package net.biancheng;
  2. import org.apache.log4j.Logger;
  3. import org.springframework.context.ApplicationContext;
  4. import org.springframework.context.support.ClassPathXmlApplicationContext;
  5. public class MainApp {
  6. static Logger log = Logger.getLogger(MainApp.class.getName());
  7. public static void main(String[] args) {
  8. ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
  9. log.info("Going to create HelloWord Obj");
  10. HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
  11. obj.getMessage();
  12. log.info("Exiting the program");
  13. }
  14. }

Beans.xml 代码如下。

 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  6. <bean id="helloWorld" class="net.biancheng.HelloWorld">
  7. <property name="message" value="Hello,bianchengbang!" />
  8. </bean>
  9. </beans>

log4j.properties 内容如下。

# Define the root logger with appender file
log4j.rootLogger = DEBUG, FILE

# Define the file appender
log4j.appender.FILE=org.apache.log4j.FileAppender
# Set the name of the file
log4j.appender.FILE.File=D:\\log.out

# Set the immediate flush to true (default)
log4j.appender.FILE.ImmediateFlush=true

# Set the threshold to debug mode
log4j.appender.FILE.Threshold=debug

# Set the append to false, overwrite
log4j.appender.FILE.Append=false

# Define the layout for file appender
log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.FILE.layout.conversionPattern=%m%n

运行结果如下。

消息:Hello,bianchengbang!

log.out 文件内容如下。

Going to create HelloWord Obj
Exiting the program

24.Spring事务(Transaction)

Spring集成Log4JSpring编程式事务管理 >

事务(Transaction)是面向关系型数据库(RDBMS)企业应用程序的重要组成部分,用来确保数据的完整性和一致性。

事务具有以下 4 个特性,即原子性、一致性、隔离性和持久性,这 4 个属性称为 ACID 特性。

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

编程式和声明式

Spring 的事务管理有 2 种方式:

  1. 传统的编程式事务管理,即通过编写代码实现的事务管理;
  2. 基于 AOP 技术实现的声明式事务管理。

1. 编程式事务管理

编程式事务管理是通过编写代码实现的事务管理,灵活性高,但难以维护。

2. 声明式事务管理

Spring 声明式事务管理在底层采用了 AOP 技术,其最大的优点在于无须通过编程的方式管理事务,只需要在配置文件中进行相关的规则声明,就可以将事务规则应用到业务逻辑中。

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

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


显然声明式事务管理要优于编程式事务管理。

事务管理接口

PlatformTransactionManager、TransactionDefinition 和 TransactionStatus 是事务的 3 个核心接口。

PlatformTransactionManager接口

PlatformTransactionManager 接口用于管理事务,接口定义如下:

 
  1. public interface PlatformTransactionManager {
  2. TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
  3. void commit(TransactionStatus status) throws TransactionException;
  4. void rollback(TransactionStatus status) throws TransactionException;
  5. }

该接口中方法说明如下:
 

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


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

TransactionDefinition接口

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

 
  1. public interface TransactionDefinition {
  2. int getPropagationBehavior();
  3. int getIsolationLevel();
  4. String getName();
  5. int getTimeout();
  6. boolean isReadOnly();
  7. }

该接口中方法说明如下。
 

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

以下是隔离级别的值。
 

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

关于事务隔离级别,详细介绍推荐阅读《数据库事务隔离级别》一节。

以下是传播行为的可能值,传播行为用来控制是否需要创建事务以及如何创建事务。
 

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

TransactionStatus接口

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

 
  1. public interface TransactionStatus extends SavepointManager {
  2. boolean isNewTransaction();
  3. boolean hasSavepoint();
  4. void setRollbackOnly();
  5. boolean isRollbackOnly();
  6. boolean isCompleted();
  7. }

该接口中方法说明如下。
 

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

Spring集成Log4JSpring编程式事务管理 >

25.Spring编程式事务管理

Spring事务Spring基于XML实现事务管理 >

编程式事务管理是通过编写代码实现的事务管理,包括定义事务的开始、正常执行后的事务提交和异常时的事务回滚。

Spring 出现以前,编程式事务管理是基于 POJO 应用的唯一选择。在 Hibernate 中,我们需要在代码中显式调用 beginTransaction()、commit()、rollback() 等事务管理相关的方法,这就是编程式事务管理。而通过 Spring 提供的事务管理 API,我们可以在代码中灵活控制事务的执行。

下面根据在《Spring事务》一节讲解的 PlatformTransactionManager、TransactionDefinition 和 TransactionStatus 三个核心接口,通过编程的方式实现事务管理。

示例

下面使用 Eclipse IDE 演示 Spring 中编程式事务管理,步骤如下:

  • 创建 SpringDemo 项目,并在 src 目录下创建 net.biancheng 包。
  • 导入 Spring 相关 JAR 包及 mysql-connector-java.x.x.x.jar 包。
  • 在 net.biancheng 包下创建 User、UserDao、UserDaoImpl、Beans.xml 和 MainApp。
  • 运行 SpringDemo 项目。


User 类代码如下。

 
  1. package net.biancheng;
  2. public class User {
  3. private int id;
  4. private String name;
  5. private int age;
  6. public User() {
  7. }
  8. public User(String name, Integer age) {
  9. this.name = name;
  10. this.age = age;
  11. }
  12. // 省略set和get方法
  13. }

UserDao 代码如下。

 
  1. package net.biancheng;
  2. import java.util.List;
  3. public interface UserDao {
  4. /**
  5. * 初始化User表
  6. */
  7. void createUserTable();
  8. /**
  9. * 保存用户
  10. */
  11. void saveUser(User user);
  12. /**
  13. * 查询用户
  14. */
  15. List<User> listUser();
  16. }

UserDaoImpl 代码如下。

 
  1. package net.biancheng;
  2. import java.sql.ResultSet;
  3. import java.sql.SQLException;
  4. import java.util.List;
  5. import javax.sql.DataSource;
  6. import org.springframework.jdbc.core.JdbcTemplate;
  7. import org.springframework.jdbc.core.RowMapper;
  8. import org.springframework.transaction.PlatformTransactionManager;
  9. import org.springframework.transaction.TransactionDefinition;
  10. import org.springframework.transaction.TransactionStatus;
  11. import org.springframework.transaction.support.DefaultTransactionDefinition;
  12. public class UserDaoImpl implements UserDao {
  13. private JdbcTemplate jdbcTemplate;
  14. private UserDao userDao;
  15. private PlatformTransactionManager transactionManager;
  16. public JdbcTemplate getJdbcTemplate() {
  17. return jdbcTemplate;
  18. }
  19. public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
  20. this.jdbcTemplate = jdbcTemplate;
  21. }
  22. public UserDao getUserDao() {
  23. return userDao;
  24. }
  25. public void setUserDao(UserDao userDao) {
  26. this.userDao = userDao;
  27. }
  28. public void setDataSource(DataSource datasource) {
  29. this.jdbcTemplate = new JdbcTemplate(datasource);
  30. }
  31. public void setTransactionManager(PlatformTransactionManager transactionManager) {
  32. this.transactionManager = transactionManager;
  33. }
  34. @Override
  35. public void createUserTable() {
  36. this.jdbcTemplate.execute("CREATE TABLE `user` (\r\n" + " `id` int(11) NOT NULL AUTO_INCREMENT,\r\n"
  37. + " `name` varchar(50) DEFAULT NULL,\r\n" + " `age` int(11) DEFAULT NULL,\r\n"
  38. + " PRIMARY KEY (`id`)\r\n" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8;");
  39. }
  40. @Override
  41. public void saveUser(User user) {
  42. TransactionDefinition def = new DefaultTransactionDefinition();
  43. // getTransaction()用于启动事务,返回TransactionStatus实例对象
  44. TransactionStatus status = transactionManager.getTransaction(def);
  45. try {
  46. this.jdbcTemplate.update("INSERT INTO USER(NAME,age) VALUES (?,?)", user.getName(), user.getAge());
  47. transactionManager.commit(status);
  48. System.out.println("commit!");
  49. } catch (Exception e) {
  50. System.out.println("Error in creating record, rolling back");
  51. transactionManager.rollback(status);
  52. throw e;
  53. }
  54. }
  55. @Override
  56. public List<User> listUser() {
  57. List<User> users = this.jdbcTemplate.query("SELECT NAME,age FROM USER", new RowMapper<User>() {
  58. public User mapRow(ResultSet rs, int rowNum) throws SQLException {
  59. User user = new User();
  60. user.setName(rs.getString("name"));
  61. user.setAge(rs.getInt("age"));
  62. return user;
  63. }
  64. });
  65. return users;
  66. }
  67. }

Beans.xml 代码如下。

 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  6. <!-- 配置数据源 -->
  7. <bean id="dataSource"
  8. class="org.springframework.jdbc.datasource.DriverManagerDataSource">
  9. <!--数据库驱动 -->
  10. <property name="driverClassName"
  11. value="com.mysql.jdbc.Driver" />
  12. <!--连接数据库的url -->
  13. <property name="url" value="jdbc:mysql://localhost/test" />
  14. <!--连接数据库的用户名 -->
  15. <property name="username" value="root" />
  16. <!--连接数据库的密码 -->
  17. <property name="password" value="root" />
  18. </bean>
  19. <!--配置JDBC模板 -->
  20. <bean id="jdbcTemplate"
  21. class="org.springframework.jdbc.core.JdbcTemplate">
  22. <!--默认必须使用数据源 -->
  23. <property name="dataSource" ref="dataSource" />
  24. </bean>
  25. <bean id="transactionManager"
  26. class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  27. <property name="dataSource" ref="dataSource" />
  28. </bean>
  29. <bean id="userdao" class="net.biancheng.UserDaoImpl">
  30. <property name="jdbcTemplate" ref="jdbcTemplate" />
  31. <property name="transactionManager" ref="transactionManager" />
  32. </bean>
  33. </beans>

MainApp 类代码如下。

 
  1. package net.biancheng;
  2. import java.util.List;
  3. import org.springframework.context.ApplicationContext;
  4. import org.springframework.context.support.ClassPathXmlApplicationContext;
  5. public class MainApp {
  6. public static void main(String[] args) {
  7. ApplicationContext ctx = new ClassPathXmlApplicationContext("Beans.xml");
  8. UserDao dao = (UserDao) ctx.getBean("userdao");
  9. dao.createUserTable();
  10. dao.saveUser(new User("bianchengbang", 12));
  11. dao.saveUser(new User("baidu", 18));
  12. List<User> users = dao.listUser();
  13. for (User user : users) {
  14. System.out.println("姓名:" + user.getName() + "\t年龄:" + user.getAge());
  15. }
  16. }
  17. }

事务提交,运行结果如下。

commit!
commit!
姓名:bianchengbang 年龄:12
姓名:baidu 年龄:18

如果您想模拟事务回滚场景,在提交事务时抛出异常即可。

26.Spring基于XML实现事务管理

Spring编程式事务管理Spring基于注解实现事务管理 >

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

声明式事务最大的优点就是不需要通过编程的方式管理事务,可以将业务逻辑代码和事务管理代码很好的分开。

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

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


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

示例

下面使用 Eclipse IDE 演示通过 XML 方式实现声明式事务管理,步骤如下:

  • 创建 SpringDemo 项目,并在 src 目录下创建 net.biancheng 包。
  • 导入 Spring 相关 JAR 包及 mysql-connector-java.x.x.x.jar 包。
  • 在 net.biancheng 包下创建 User、UserDao、UserDaoImpl、Beans.xml 和 MainApp。
  • 运行 SpringDemo 项目。


User 类代码如下。

 
  1. package net.biancheng;
  2. public class User {
  3. private int id;
  4. private String name;
  5. private int age;
  6. public User() {
  7. }
  8. public User(String name, Integer age) {
  9. this.name = name;
  10. this.age = age;
  11. }
  12. // 省略set和get方法
  13. }

UserDao 代码如下。

 
  1. package net.biancheng;
  2. import java.util.List;
  3. public interface UserDao {
  4. /**
  5. * 初始化User表
  6. */
  7. void createUserTable();
  8. /**
  9. * 保存用户
  10. */
  11. void saveUser(User user);
  12. /**
  13. * 查询用户
  14. */
  15. List<User> listUser();
  16. }

UserDaoImpl 代码如下。

 
  1. package net.biancheng;
  2. import java.sql.ResultSet;
  3. import java.sql.SQLException;
  4. import java.util.List;
  5. import javax.sql.DataSource;
  6. import org.springframework.jdbc.core.JdbcTemplate;
  7. import org.springframework.jdbc.core.RowMapper;
  8. public class UserDaoImpl implements UserDao {
  9. private JdbcTemplate jdbcTemplate;
  10. private UserDao userDao;
  11. public JdbcTemplate getJdbcTemplate() {
  12. return jdbcTemplate;
  13. }
  14. public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
  15. this.jdbcTemplate = jdbcTemplate;
  16. }
  17. public UserDao getUserDao() {
  18. return userDao;
  19. }
  20. public void setUserDao(UserDao userDao) {
  21. this.userDao = userDao;
  22. }
  23. public void setDataSource(DataSource datasource) {
  24. this.jdbcTemplate = new JdbcTemplate(datasource);
  25. }
  26. @Override
  27. public void createUserTable() {
  28. this.jdbcTemplate.execute("CREATE TABLE `user` (\r\n" + " `id` int(11) NOT NULL AUTO_INCREMENT,\r\n"
  29. + " `name` varchar(50) DEFAULT NULL,\r\n" + " `age` int(4) DEFAULT NULL,\r\n"
  30. + " PRIMARY KEY (`id`)\r\n" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8;");
  31. }
  32. @Override
  33. public void saveUser(User user) {
  34. try {
  35. this.jdbcTemplate.update("INSERT INTO USER(NAME,age) VALUES (?,?)", user.getName(), user.getAge());
  36. throw new RuntimeException("simulate Error condition");
  37. } catch (Exception e) {
  38. System.out.println("Error in creating record, rolling back");
  39. throw e;
  40. }
  41. }
  42. @Override
  43. public List<User> listUser() {
  44. List<User> users = this.jdbcTemplate.query("SELECT NAME,age FROM USER", new RowMapper<User>() {
  45. public User mapRow(ResultSet rs, int rowNum) throws SQLException {
  46. User user = new User();
  47. user.setName(rs.getString("name"));
  48. user.setAge(rs.getInt("age"));
  49. return user;
  50. }
  51. });
  52. return users;
  53. }
  54. }

Beans.xml 代码如下。

 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:tx="http://www.springframework.org/schema/tx"
  5. xmlns:aop="http://www.springframework.org/schema/aop"
  6. xsi:schemaLocation="http://www.springframework.org/schema/beans
  7. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  8. http://www.springframework.org/schema/tx
  9. http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
  10. http://www.springframework.org/schema/aop
  11. http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
  12. <!-- 配置数据源 -->
  13. <bean id="dataSource"
  14. class="org.springframework.jdbc.datasource.DriverManagerDataSource">
  15. <!--数据库驱动 -->
  16. <property name="driverClassName"
  17. value="com.mysql.jdbc.Driver" />
  18. <!--连接数据库的url -->
  19. <property name="url" value="jdbc:mysql://localhost/test" />
  20. <!--连接数据库的用户名 -->
  21. <property name="username" value="root" />
  22. <!--连接数据库的密码 -->
  23. <property name="password" value="root" />
  24. </bean>
  25. <!-- 编写通知:对事务进行增强(通知),需要编写切入点和具体执行事务的细节 -->
  26. <tx:advice id="txAdvice"
  27. transaction-manager="transactionManager">
  28. <tx:attributes>
  29.  <!-- 给切入点方法添加事务详情,name表示方法名称,*表示任意方法名称,propagation用于设置传播行为,read-only表示隔离级别,是否只读 -->
  30. <tx:method name="*" propagation="SUPPORTS" readOnly = "false"/>
  31. </tx:attributes>
  32. </tx:advice>
  33. <!-- aop编写,让Spring自动对目标生成代理,需要使用AspectJ的表达式 -->
  34. <aop:config>
  35. <!-- 切入点,execution 定义的表达式表示net.biencheng包下的所有类所有方法都应用该是事务 -->
  36. <aop:pointcut id="createOperation"
  37. expression="execution(* net.biancheng.*.*(..))" />
  38. <aop:advisor advice-ref="txAdvice"
  39. pointcut-ref="createOperation" />
  40. </aop:config>
  41. <bean id="transactionManager"
  42. class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  43. <property name="dataSource" ref="dataSource" />
  44. </bean>
  45. <bean id="jdbcTemplate"
  46. class="org.springframework.jdbc.core.JdbcTemplate">
  47. <property name="dataSource" ref="dataSource" />
  48. </bean>
  49. <bean id="userdao" class="net.biancheng.UserDaoImpl">
  50. <property name="dataSource" ref="dataSource" />
  51. <property name="jdbcTemplate" ref="jdbcTemplate" />
  52. </bean>
  53. </beans>

MainApp 类代码如下。

 
  1. package net.biancheng;
  2. import java.util.List;
  3. import org.springframework.context.ApplicationContext;
  4. import org.springframework.context.support.ClassPathXmlApplicationContext;
  5. public class MainApp {
  6. public static void main(String[] args) {
  7. ApplicationContext ctx = new ClassPathXmlApplicationContext("Beans.xml");
  8. UserDao dao = (UserDao) ctx.getBean("userdao");
  9. dao.createUserTable();
  10. dao.saveUser(new User("bianchengbang", 12));
  11. dao.saveUser(new User("baidu", 18));
  12. List<User> users = dao.listUser();
  13. for (User user : users) {
  14. System.out.println("姓名:" + user.getName() + "\t年龄:" + user.getAge());
  15. }
  16. }
  17. }

运行结果如下。

Error in creating record, rolling back
Exception in thread "main" java.lang.RuntimeException: simulate Error condition
...

27.Spring基于注解实现事务管理

Spring基于XML实现事务管理SpEL表达式语言 >

在 Spring 中,声明式事务除了可以使用 XML 实现外,还可以使用 Annotation 注解。使用注解实现可以减少代码之间的耦合度。

使用 Annotation 的方式非常简单,只需要在项目中做两件事,具体如下。

1)在 Spring 容器中注册驱动,代码如下所示:

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

2)在需要使用事务的业务类或者方法中添加注解 @Transactional,并配置 @Transactional 的参数。关于 @Transactional 的参数如图 1 所示。

常用属性说明如下:

  • propagation:设置事务的传播行为;
  • isolation:设置事务的隔离级别;
  • readOnly:设置是读写事务还是只读事务;
  • timeout:事务超时事件(单位:s)。

示例

下面使用 Eclipse IDE 演示使用注解实现声明式事务管理,步骤如下:

  • 创建 SpringDemo 项目,并在 src 目录下创建 net.biancheng 包。
  • 导入 Spring 相关 JAR 包及 mysql-connector-java.x.x.x.jar 包。
  • 在 net.biancheng 包下创建 User、UserDao、UserDaoImpl、Beans.xml 和 MainApp。
  • 运行 SpringDemo 项目。


User 类代码如下。

 
  1. package net.biancheng;
  2. public class User {
  3. private int id;
  4. private String name;
  5. private int age;
  6. public User() {
  7. }
  8. public User(String name, Integer age) {
  9. this.name = name;
  10. this.age = age;
  11. }
  12. // 省略set和get方法
  13. }

UserDao 代码如下。

 
  1. package net.biancheng;
  2. import java.util.List;
  3. public interface UserDao {
  4. /**
  5. * 初始化User表
  6. */
  7. void createUserTable();
  8. /**
  9. * 保存用户
  10. */
  11. void saveUser(User user);
  12. /**
  13. * 查询用户
  14. */
  15. List<User> listUser();
  16. }

UserDaoImpl 代码如下。

 
  1. package net.biancheng;
  2. import java.sql.ResultSet;
  3. import java.sql.SQLException;
  4. import java.util.List;
  5. import javax.sql.DataSource;
  6. import org.springframework.jdbc.core.JdbcTemplate;
  7. import org.springframework.jdbc.core.RowMapper;
  8. import org.springframework.transaction.annotation.Isolation;
  9. import org.springframework.transaction.annotation.Propagation;
  10. import org.springframework.transaction.annotation.Transactional;
  11. @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false)
  12. public class UserDaoImpl implements UserDao {
  13. private JdbcTemplate jdbcTemplate;
  14. private UserDao userDao;
  15. public JdbcTemplate getJdbcTemplate() {
  16. return jdbcTemplate;
  17. }
  18. public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
  19. this.jdbcTemplate = jdbcTemplate;
  20. }
  21. public UserDao getUserDao() {
  22. return userDao;
  23. }
  24. public void setUserDao(UserDao userDao) {
  25. this.userDao = userDao;
  26. }
  27. public void setDataSource(DataSource datasource) {
  28. this.jdbcTemplate = new JdbcTemplate(datasource);
  29. }
  30. @Override
  31. public void createUserTable() {
  32. this.jdbcTemplate.execute("CREATE TABLE `user` (\r\n" + " `id` int(11) NOT NULL AUTO_INCREMENT,\r\n"
  33. + " `name` varchar(50) DEFAULT NULL,\r\n" + " `age` int(4) DEFAULT NULL,\r\n"
  34. + " PRIMARY KEY (`id`)\r\n" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8;");
  35. }
  36. @Override
  37. public void saveUser(User user) {
  38. try {
  39. this.jdbcTemplate.update("INSERT INTO USER(NAME,age) VALUES (?,?)", user.getName(), user.getAge());
  40. this.jdbcTemplate.update("INSERT INTO USER(NAME,age) VALUES (?,?)", "google", 16);
  41. throw new RuntimeException("simulate Error condition");
  42. } catch (Exception e) {
  43. System.out.println("Error in creating record, rolling back");
  44. throw e;
  45. }
  46. }
  47. @Override
  48. public List<User> listUser() {
  49. List<User> users = this.jdbcTemplate.query("SELECT NAME,age FROM USER", new RowMapper<User>() {
  50. public User mapRow(ResultSet rs, int rowNum) throws SQLException {
  51. User user = new User();
  52. user.setName(rs.getString("name"));
  53. user.setAge(rs.getInt("age"));
  54. return user;
  55. }
  56. });
  57. return users;
  58. }
  59. }

@Transactional 注解的参数之间用“,”进行分隔

Beans.xml 代码如下。

 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:tx="http://www.springframework.org/schema/tx"
  5. xmlns:aop="http://www.springframework.org/schema/aop"
  6. xsi:schemaLocation="http://www.springframework.org/schema/beans
  7. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  8. http://www.springframework.org/schema/tx
  9. http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
  10. http://www.springframework.org/schema/aop
  11. http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
  12. <!-- 配置数据源 -->
  13. <bean id="dataSource"
  14. class="org.springframework.jdbc.datasource.DriverManagerDataSource">
  15. <!--数据库驱动 -->
  16. <property name="driverClassName"
  17. value="com.mysql.jdbc.Driver" />
  18. <!--连接数据库的url -->
  19. <property name="url" value="jdbc:mysql://localhost/test" />
  20. <!--连接数据库的用户名 -->
  21. <property name="username" value="root" />
  22. <!--连接数据库的密码 -->
  23. <property name="password" value="root" />
  24. </bean>
  25. <!-- 配置事务管理器 -->
  26. <bean id="transactionManager"
  27. class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  28. <property name="dataSource" ref="dataSource" />
  29. </bean>
  30. <bean id="jdbcTemplate"
  31. class="org.springframework.jdbc.core.JdbcTemplate">
  32. <property name="dataSource" ref="dataSource" />
  33. </bean>
  34. <bean id="userdao" class="net.biancheng.UserDaoImpl">
  35. <property name="dataSource" ref="dataSource" />
  36. <property name="jdbcTemplate" ref="jdbcTemplate" />
  37. </bean>
  38. <!-- 注册事务管理驱动 -->
  39. <tx:annotation-driven
  40. transaction-manager="transactionManager" />
  41. </beans>

MainApp 类代码如下。

 
  1. package net.biancheng;
  2. import java.util.List;
  3. import org.springframework.context.ApplicationContext;
  4. import org.springframework.context.support.ClassPathXmlApplicationContext;
  5. public class MainApp {
  6. public static void main(String[] args) {
  7. ApplicationContext ctx = new ClassPathXmlApplicationContext("Beans.xml");
  8. UserDao dao = (UserDao) ctx.getBean("userdao");
  9. dao.createUserTable();
  10. dao.saveUser(new User("bianchengbang", 12));
  11. dao.saveUser(new User("baidu", 18));
  12. List<User> users = dao.listUser();
  13. for (User user : users) {
  14. System.out.println("姓名:" + user.getName() + "\t年龄:" + user.getAge());
  15. }
  16. }
  17. }

运行结果如下。

Error in creating record, rolling back
Exception in thread "main" java.lang.RuntimeException: simulate Error condition
...

28.SpEL表达式语言(Spring)

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 表达式中的关键字不区分大小写。

示例

例 1

下面使用 SpEL 输出一个简单的字符串“Hello,编程帮”。


  1. package net.biancheng;
  2. import org.springframework.expression.Expression;
  3. import org.springframework.expression.ExpressionParser;
  4. import org.springframework.expression.spel.standard.SpelExpressionParser;
  5. public class Test {
  6. public static void main(String[] args) {
  7. // 构造解析器
  8. ExpressionParser parser = new SpelExpressionParser();
  9. // 解析器解析字符串表达式
  10. Expression exp = parser.parseExpression("'Hello,编程帮'");
  11. // 获取表达式的值
  12. String message = (String) exp.getValue();
  13. System.out.println(message);
  14. // OR
  15. // System.out.println(parser.parseExpression("'Hello,编程帮'").getValue());
  16. }
  17. }
输出结果如下。

Hello,编程帮


SpEL 还可以调用方法、访问属性和调用构造函数。

例 2

使用 SpEL 调用 concat() 方法,代码如下。


  1. package net.biancheng;
  2. import org.springframework.expression.Expression;
  3. import org.springframework.expression.ExpressionParser;
  4. import org.springframework.expression.spel.standard.SpelExpressionParser;
  5. public class Test {
  6. public static void main(String[] args) {
  7. ExpressionParser parser = new SpelExpressionParser();
  8. Expression exp = parser.parseExpression("'Welcome,编程帮'.concat('!')");
  9. String message = (String) exp.getValue();
  10. System.out.println(message);
  11. }
  12. }
输出结果如下。

Welcome,编程帮!

例 3

使用 SpEL 调用 String 的属性 bytes,将字符串转换为字节数组,代码如下。


  1. package net.biancheng;
  2. import org.springframework.expression.Expression;
  3. import org.springframework.expression.ExpressionParser;
  4. import org.springframework.expression.spel.standard.SpelExpressionParser;
  5. public class Test {
  6. public static void main(String[] args) {
  7. ExpressionParser parser = new SpelExpressionParser();
  8. Expression exp = parser.parseExpression("'Hello 编程帮'.bytes");
  9. byte[] bytes = (byte[]) exp.getValue();
  10. for (int i = 0; i < bytes.length; i++) {
  11. System.out.print(bytes[i] + " ");
  12. }
  13. }
  14. }
输出结果如下。

72 101 108 108 111 32 -79 -32 -77 -52 -80 -17 

例 4

SpEL 还支持使用嵌套属性,下面将字符串转换为字节后获取长度,代码如下。


  1. package net.biancheng;
  2. import org.springframework.expression.Expression;
  3. import org.springframework.expression.ExpressionParser;
  4. import org.springframework.expression.spel.standard.SpelExpressionParser;
  5. public class Test {
  6. public static void main(String[] args) {
  7. ExpressionParser parser = new SpelExpressionParser();
  8. Expression exp = parser.parseExpression("'Hello 编程帮'.bytes.length");
  9. int length = (Integer) exp.getValue();
  10. System.out.println(length);
  11. }
  12. }
输出结果如下。

12

例 5

字符串的构造函数可以被调用,而不是使用字符串文本,下面将字符串内容转换为大写字母,代码如下。


  1. package net.biancheng;
  2. import org.springframework.expression.Expression;
  3. import org.springframework.expression.ExpressionParser;
  4. import org.springframework.expression.spel.standard.SpelExpressionParser;
  5. public class Test {
  6. public static void main(String[] args) {
  7. ExpressionParser parser = new SpelExpressionParser();
  8. Expression exp = parser.parseExpression("new String('hello bianchengbang').toUpperCase()");
  9. String message = exp.getValue(String.class);
  10. System.out.println(message);
  11. // OR
  12. // System.out.println(parser.parseExpression("'hello
  13. // bianchengbang'.toUpperCase()").getValue());
  14. }
  15. }
输出结果如下。

HELLO BIANCHENGBANG

SpEL对Bean定义的支持

SpEL 表达式可以与 XML 或基于注解的配置元数据一起使用,SpEL 表达式以#{开头,以}结尾,如#{'Hello'}

1. 基于XML的配置

可以使用以下表达式来设置属性或构造函数的参数值。


  1. <bean id="number" class="net.biancheng.Number">
  2. <property name="randomNumber" value="#{T(java.lang.Math).random() * 100.0}"/>
  3. </bean>
也可以通过名称引用其它 Bean 属性,如以下代码。


  1. <bean id="shapeGuess" class="net.biancheng.ShapeGuess">
  2. <property name="shapSeed" value="#{number.randomNumber}"/>
  3. </bean>

2. 基于注解的配置

@Value 注解可以放在字段、方法、以及构造函数参数上,以指定默认值。

以下是一个设置字段变量默认值的例子。


  1. public static class FieldValueTestBean
  2. @value("#{ systemProperties[ 'user.region'] }")
  3. private String defaultLocale;
  4. public void setDefaultLocale (String defaultLocale) {
  5. this.defaultLocale = defaultLocale;
  6. }
  7. public string getDefaultLocale() {
  8. return this.defaultLocale;
  9. }
  10. }

SpEL中的运算符

我们可以在 SpEL 中使用运算符,例如算术运算符、关系运算符、逻辑运算符。

例 6



  1. package net.biancheng;
  2. import org.springframework.expression.ExpressionParser;
  3. import org.springframework.expression.spel.standard.SpelExpressionParser;
  4. public class Test {
  5. public static void main(String[] args) {
  6. ExpressionParser parser = new SpelExpressionParser();
  7. // 算术运算符
  8. System.out.println(parser.parseExpression("'Hello SPEL'+'!'").getValue());
  9. System.out.println(parser.parseExpression("10 * 10/2").getValue());
  10. System.out.println(parser.parseExpression("'今天是:'+ new java.util.Date()").getValue());
  11. // 逻辑运算符
  12. System.out.println(parser.parseExpression("true and true").getValue());
  13. // 关系运算符
  14. System.out.println(parser.parseExpression("'sonoo'.length()==5").getValue());
  15. }
  16. }

输出结果为

Hello SPEL!
50
今天是:Fri Feb 05 09:31:46 CST 2021
true
true

SpEL中的变量

在 SpEL 中,我们可以定义变量,并在方法中使用。定义变量需要用到 StandardEvaluationContext 类。

例 7

Calculation 类代码如下。


  1. package net.biancheng;
  2. public class Calculation {
  3. private int number;
  4. public int getNumber() {
  5. return number;
  6. }
  7. public void setNumber(int number) {
  8. this.number = number;
  9. }
  10. public int cube() {
  11. return number * number * number;
  12. }
  13. }

Test 类代码如下。


  1. package net.biancheng;
  2. import org.springframework.expression.ExpressionParser;
  3. import org.springframework.expression.spel.standard.SpelExpressionParser;
  4. import org.springframework.expression.spel.support.StandardEvaluationContext;
  5. public class Test {
  6. public static void main(String[] args) {
  7. Calculation calculation = new Calculation();
  8. StandardEvaluationContext context = new StandardEvaluationContext(calculation);
  9. ExpressionParser parser = new SpelExpressionParser();
  10. parser.parseExpression("number").setValue(context, "5");
  11. System.out.println(calculation.cube());
  12. }
  13. }
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值