Spring体系结构

一 Spring体系结构

Spring 框架采用分层的理念,根据功能的不同划分成了多个模块,这些模块大体可分为 Data Access/Integration(数据访问与集成)、Web、AOP、Aspects、Instrumentation(检测)、Messaging(消息处理)、Core Container(核心容器)和 Test。如下图所示(以下是 Spring Framework 4.x 版本后的系统架构图)
上图中包含了 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. A O P 、 A s p e c t s 、 I n s t r u m e n t a t i o n 和 M e s s a g i n g \textcolor{red}{AOP、Aspects、Instrumentation和Messaging} AOPAspectsInstrumentationMessaging

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

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

5. Test模块

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

c o l o r S p r i n g 开 发 环 境 搭 建 \textcolor{red}{colorSpring开发环境搭建} colorSpring

三 第一个Spring程序

1. 创建Java项目

在 Eclipse 中创建一个简单的 Java 项目,依次单击 File -> New -> Java Project,这里将项目名称设置为 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 类的代码如下。

package net.biancheng;
public class HelloWorld {
    private String message;
    public void setMessage(String message) {
        this.message = message;
    }
    public void getMessage() {
        System.out.println("message : " + message);
    }
}

MainApp.java 类的代码如下。

package net.biancheng;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
        obj.getMessage();
    }
}

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

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

4. 创建配置文件

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

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

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

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

5. 运行程序

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

message : Hello World!

四 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 中添加如下代码:

<!--指定Spring配置文件的位置,有多个配置文件时,以逗号分隔-->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <!--spring将加载spring目录下的applicationContext.xml文件-->
    <param-value>
        classpath:spring/applicationContext.xml
    </param-value>
</context-param>
<!--指定以ContextLoaderListener方式启动Spring容器-->
<listener>
    <listener-class>
        org.springframework.web.context.ContextLoaderListener
    </listener-class>
</listener>

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

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

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

五 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 配置文件的根元素是 ,该元素包含了多个子元素 。每一个 元素都定义了一个 Bean,并描述了该 Bean 如何被装配到 Spring 容器中。

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



    
        
    

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

bean>元素的常用属性

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

六 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 中有效。

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

例 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 项目。
  6. HelloWorld 类代码如下。
package net.biancheng;
public class HelloWorld {
    private String message;
    public void setMessage(String message) {
        this.message = message;
    }
    public void getMessage() {
        System.out.println("message : " + message);
    }
}

MainApp 类如下。

package net.biancheng;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        HelloWorld objA = (HelloWorld) context.getBean("helloWorld");
        objA.setMessage("对象A");
        objA.getMessage();
        HelloWorld objB = (HelloWorld) context.getBean("helloWorld");
        objB.getMessage();
    }
}

Beans.xml 文件内容如下。



    
      

七 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 的生命周期流程如下图所示。

Spring体系结构_第1张图片

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 的作用域为 singleton,则将该 Bean 放入 Spring IoC 的缓存池中,触发 Spring 对该 Bean 的生命周期管理;如果在 中指定了该 Bean 的作用域为 prototype,则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该 Bean。
  11. 如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法销毁 Bean;如果在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁。

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

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

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

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

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

初始化回调

1. 使用接口

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

void afterPropertiesSet() throws Exception;

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


public class User implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("调用接口:InitializingBean,方法:afterPropertiesSet,无参数");
    }
}

2. 配置XML

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


public class User {
    public void init() {
        System.out.println("调用init-method指定的初始化方法:init" );
    }
}

3. 使用注解

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

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

销毁回调

1. 使用接口

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

void destroy() throws Exception;

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

纯文本复制
<bean id="..." class="..." />public class User implements InitializingBean {    @Override    public void afterPropertiesSet() throws Exception {        System.out.println("调用接口:InitializingBean,方法:afterPropertiesSet,无参数");    }}

2. 配置XML

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

public class User {    public void destroy() {        System.out.println("调用destroy-method指定的销毁方法:destroy" );    }}

3. 使用注解

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

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

示例

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

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

HelloWorld 类代码如下。

package net.biancheng;public class HelloWorld {    private String message;    public void setMessage(String message) {        this.message = message;    }    public void getMessage() {        System.out.println("message : " + message);    }    public void init() {        System.out.println("Bean正在进行初始化");    }    public void destroy() {        System.out.println("Bean将要被销毁");    }}

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

package net.biancheng;import org.springframework.context.support.AbstractApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class MainApp {    public static void main(String[] args) {        AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");        HelloWorld obj = (HelloWorld) context.getBean("helloWorld");        obj.getMessage();        context.registerShutdownHook();    }}

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

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

运行结果如下。

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

默认的初始化和销毁方法

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

纯文本复制
            ...    

八 BeanPostProcessor(Spring后置处理器)

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

BeanPostProcessor 接口源码如下:

public interface BeanPostProcessor {    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;}

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 类代码如下。

package net.biancheng;public class HelloWorld {   
    private String message;   
    public void setMessage(String message) {        
    this.message = message;    }   
    public void getMessage() {       
        System.out.println("Message : " + message);    }  
    public void init() {       
        System.out.println("Bean正在初始化");    }   
    public void destroy() {        System.out.println("Bean将要被销毁");    }}

InitHelloWorld 类代码如下。

package net.biancheng;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
public class InitHelloWorld implements BeanPostProcessor, Ordered {   
    @Override   
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {        System.out.println("A Before : " + beanName);      
                                                                                                       return bean;    }   
    @Override   
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {        System.out.println("A After : " + beanName);      
                                                                                                      return bean;    }   
    @Override    public int getOrder() {     
        return 5;    }}

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

InitHelloWorld2 的代码如下。

package net.biancheng;import org.springframework.beans.BeansException;import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;public class InitHelloWorld2 implements BeanPostProcessor, Ordered {   
    @Override   
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {        System.out.println("B Before : " + beanName);    
                                                                                                       return bean;    }   
    @Override   
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {        System.out.println("B After : " + beanName);      
       return bean;    }   
    @Override   
    public int getOrder() {      
        return 0;    }}

Beans.xml 代码如下。

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

MainApp 类代码如下。

package net.biancheng;import org.springframework.context.support.AbstractApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class MainApp {    public static void main(String[] args) {        AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");        HelloWorld obj = (HelloWorld) context.getBean("helloWorld");        obj.getMessage();        context.registerShutdownHook();    }}

运行结果如下。

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

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

九 Spring Bean继承

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 类代码如下。

package net.biancheng;public class HelloWorld {    private String message1;    private String message2;    public void setMessage1(String message) {        this.message1 = message;    }    public void setMessage2(String message) {        this.message2 = message;    }    public void getMessage1() {        System.out.println("World Message1 : " + message1);    }    public void getMessage2() {        System.out.println("World Message2 : " + message2);    }}

HelloChina 类代码如下。

package net.biancheng;public class HelloChina {    private String message1;    private String message2;    private String message3;    public void setMessage1(String message) {        this.message1 = message;    }    public void setMessage2(String message) {        this.message2 = message;    }    public void setMessage3(String message) {        this.message3 = message;    }    public void getMessage1() {        System.out.println("China Message1 : " + message1);    }    public void getMessage2() {        System.out.println("China Message2 : " + message2);    }    public void getMessage3() {        System.out.println("China Message3 : " + message3);    }}

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

                                                   

MainApp 类代码如下。

package net.biancheng;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class MainApp {    public static void main(String[] args) {        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");        HelloWorld objA = (HelloWorld) context.getBean("helloWorld");        objA.getMessage1();        objA.getMessage2();        HelloChina objB = (HelloChina) context.getBean("helloChina");        objB.getMessage1();        objB.getMessage2();        objB.getMessage3();    }}

运行结果如下。

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 可以继承父 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 类代码如下。

package net.biancheng;
public class HelloWorld {
    private String message1;
    private String message2;
    public void setMessage1(String message) {
        this.message1 = message;
    }
    public void setMessage2(String message) {
        this.message2 = message;
    }
    public void getMessage1() {
        System.out.println("World Message1 : " + message1);
    }
    public void getMessage2() {
        System.out.println("World Message2 : " + message2);
    }
}

HelloChina 类代码如下。

package net.biancheng;
public class HelloChina {
    private String message1;
    private String message2;
    private String message3;
    public void setMessage1(String message) {
        this.message1 = message;
    }
    public void setMessage2(String message) {
        this.message2 = message;
    }
    public void setMessage3(String message) {
        this.message3 = message;
    }
    public void getMessage1() {
        System.out.println("China Message1 : " + message1);
    }
    public void getMessage2() {
        System.out.println("China Message2 : " + message2);
    }
    public void getMessage3() {
        System.out.println("China Message3 : " + message3);
    }
}

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

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
    <bean id="helloWorld" class="net.biancheng.HelloWorld">
        <property name="message1" value="Hello World!" />
        <property name="message2" value="Hello World2!" />
    </bean>
   
    <bean id="helloChina" class="net.biancheng.HelloChina"
        parent="helloWorld">
        <property name="message1" value="Hello China!" />
        <property name="message3" value="Hello China3!" />
    </bean>
</beans>

MainApp 类代码如下。

package net.biancheng;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        HelloWorld objA = (HelloWorld) context.getBean("helloWorld");
        objA.getMessage1();
        objA.getMessage2();
        HelloChina objB = (HelloChina) context.getBean("helloChina");
        objB.getMessage1();
        objB.getMessage2();
        objB.getMessage3();
    }
}

运行结果如下。

World Message1 : Hello WorldWorld Message2 : Hello World2China Message1 : Hello ChinaChina Message2 : Hello World2China Message3 : Hello China3

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

Bean定义模板

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

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
    <bean id="beanTeamplate" abstract="true">
        <property name="message1" value="Hello World!" />
        <property name="message2" value="Hello World2!" />
        <property name="message3" value="Hello World3!" />
    </bean>
    <bean id="helloChina" class="net.biancheng.HelloChina"
        parent="beanTeamplate">
        <property name="message1" value="Hello China!" />
        <property name="message3" value="Hello China!" />
    </bean>
</beans>

十 Spring依赖注入

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

构造函数注入

下面使用 标签实现构造函数注入。

在 标签中,包含 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 类代码如下。

package net.biancheng;
public class Person {
    private Man man;
    public Person(Man man) {
        System.out.println("在Person的有参构造函数内");
        this.man = man;
    }
    public void man() {
        man.show();
    }
}

Man 类代码如下。

package net.biancheng;
public class Man {
    private String name;
    private int age;
    public Man() {
        System.out.println("在man的构造函数内");
    }
    public Man(String name, int age) {
        System.out.println("在man的有参构造函数内");
        this.name = name;
        this.age = age;
    }
    public void show() {
        System.out.println("名称:" + name + "\n年龄:" + age);
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

Beans.xml 配置文件如下。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
    <bean id="man" class="net.biancheng.Man">
        <constructor-arg value="bianchengbang" />
        <constructor-arg value="12" type="int" />
    </bean>
    <bean id="person" class="net.biancheng.Person">
        <constructor-arg ref="man" type="net.biancheng.Man"/>
    </bean>
</beans>

MainApp 类代码如下。

package net.biancheng;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        Person person = (Person) context.getBean("person");
        person.man();
    }
}

运行结果如下。

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

setter注入

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

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

例 2

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

package net.biancheng;
public class Person {
    private Man man;
    public void man() {
        man.show();
    }
    public Man getMan() {
        return man;
    }
    public void setMan(Man man) {
        System.out.println("在setMan方法内");
        this.man = man;
    }
}

Beans.xml 配置文件如下。

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

运行结果如下。

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

十一 Spring注入内部Bean

Java 中在类内部定义的类称为内部类,同理在 Bean 中定义的 Bean 称为内部 Bean。注入内部 Bean 使用 和 中的 标签。如下所示。

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

内部 Bean 的定义不需要指定 id 和 name 。如果指定了,容器也不会将其作为区分 Bean 的标识符,反而会无视内部 Bean 的 scope 属性。所以内部 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 类代码如下。

package net.biancheng;
public class Person {
    private Man man;
    public Man getMan() {
        return man;
    }
    public void setMan(Man man) {
        System.out.println("在setMan方法内");
        this.man = man;
    }
    public void man() {
        man.show();
    }
}

Man 类代码如下。

package net.biancheng;
public class Man {
    private String name;
    private int age;
    public Man() {
        System.out.println("在man的构造函数内");
    }
    public Man(String name, int age) {
        System.out.println("在man的有参构造函数内");
        this.name = name;
        this.age = age;
    }
    public void show() {
        System.out.println("名称:" + name + "\n年龄:" + age);
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

Beans.xml 配置文件如下。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
    <bean id="person" class="net.biancheng.Person">
        <property name="man">
            <bean class="net.biancheng.Man">
                <property name="name" value="bianchengbang" />
                <property name="age" value="12" />
            </bean>
        </property>
    </bean>
</beans>

MainApp 类代码如下。

package net.biancheng;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        Person person = (Person) context.getBean("person");
        person.man();
    }
}

十二 Spring Bean自动装配

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

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

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

使用自动装配需要配置 元素的 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 项目。

  6. Person 类代码如下。

    package net.biancheng;
    public class Person {
        private Man man;
        public Person() {
            System.out.println("在Person的构造函数内");
        }
        public Person(Man man) {
            System.out.println("在Person的有参构造函数内");
            this.man = man;
        }
        public void man() {
            man.show();
        }
        public Man getMan() {
            return man;
        }
        public void setMan(Man man) {
            this.man = man;
        }
    }
    

    Man 类代码如下。

    package net.biancheng;
    public class Man {
        private String name;
        private int age;
        public Man() {
            System.out.println("在man的构造函数内");
        }
        public Man(String name, int age) {
            System.out.println("在man的有参构造函数内");
            this.name = name;
            this.age = age;
        }
        public void show() {
            System.out.println("名称:" + name + "\n年龄:" + age);
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }
    }
    

    MainApp 类代码如下。

    package net.biancheng;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    public class MainApp {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
            Person person = (Person) context.getBean("person");
            person.man();
        }
    }	
    

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

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

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
        <bean id="man" class="net.biancheng.Man">
            <constructor-arg value="bianchengbang" />
            <constructor-arg value="12" type="int" />
        </bean>
        <bean id="person" class="net.biancheng.Person" autowire="no">
            <constructor-arg ref="man" type="net.biancheng.Man"/>
        </bean>
    </beans>
    

    运行结果如下。

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

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

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

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

    运行结果如下。

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

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

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

    <bean id="man1" class="net.biancheng.Man">
        <constructor-arg value="bianchengbang" />
        <constructor-arg value="12" type="int" />
    </bean>
    <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。配置文件内容修改如下。

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

    运行结果如下。

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

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

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

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

    异常信息为:

    在man的有参构造函数内
    在man的有参构造函数内
    在Person的构造函数内
    一月 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”)

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

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

    运行结果如下。

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

    自动装配的优缺点

    优点

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

    缺点

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

十三 Spring基于注解装配Bean

在 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 注解的参数指定。

十四 Spring AOP(面向切面编程)

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 个方法。

class A{ 
    public void m1(){...} 
    public void m2(){...} 
    public void m3(){...} 
    public void m4(){...} 
    public void m5(){...} 
    public void n1(){...} 
    public void n2(){...} 
    public void p1(){...} 
    public void p2(){...} 
    public void p3(){...} 
}  

以 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 来补充。
  • 可以对业务逻辑的各个部分进行隔离,从而使业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时也提高了开发效率。

十五 Spring JDK动态代理

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

示例

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

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

UserManager 类代码如下。

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

UserManagerImpl 类代码如下。

package net.biancheng;
public class UserManagerImpl implements UserManager {
    @Override
    public void addUser(String userName, String password) {
        System.out.println("正在执行添加用户方法");
        System.out.println("用户名称: " + userName + " 密码: " + password);
    }
    @Override
    public void delUser(String userName) {
        System.out.println("正在执行删除用户方法");
        System.out.println("用户名称: " + userName);
    }
}

MyAspect 类代码如下。

package net.biancheng;
public class MyAspect {
    public void myBefore() {
        System.out.println("方法执行之前");
    }
    public void myAfter() {
        System.out.println("方法执行之后");
    }
}

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

JdkProxy 类代码如下。

package net.biancheng;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* JDK动态代理实现InvocationHandler接口
*
* @author 编程帮
*
*/
public class JdkProxy implements InvocationHandler {
    private Object target; // 需要代理的目标对象
    final MyAspect myAspect = new MyAspect();
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        myAspect.myBefore();
        Object result = method.invoke(target, args);
        myAspect.myAfter();
        return result;
    }
    // 定义获取代理对象方法
    private Object getJDKProxy(Object targetObject) {
       
        // 为目标对象target赋值
        this.target = targetObject;
       
        // JDK动态代理只能代理实现了接口的类,从 newProxyInstance 函数所需的参数就可以看出来
        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(),
                this);
    }
    public static void main(String[] args) {
        JdkProxy jdkProxy = new JdkProxy();    // 实例化JDKProxy对象
        UserManager user = (UserManager) jdkProxy.getJDKProxy(new UserManagerImpl());    // 获取代理对象
        user.addUser("bianchengbang", "www.biancheng.net");    // 执行新增方法
        user.delUser("bianchengbang");    // 执行删除方法
    }
}

运行结果如下。

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

十六 Spring CGLlB动态代理

通过学习《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 项目。

  • JDK代理和CGLIB代理的区别

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

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

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

    JDK动态代理特点

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

    CGLIB动态代理特点

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

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

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

    十七 Spring集成AspectJ

    ​ 在《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 包下载地址:https://www.eclipse.org/aspectj/downloads.php

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

    • 基于 XML 的声明式 AspectJ
    • 基于 Annotation 的声明式 AspectJ

    AspectJ包下载缓慢解决方法

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

    Spring体系结构_第2张图片

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

    Spring体系结构_第3张图片

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

    Spring体系结构_第4张图片

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

    十八 Spring AOP:基于AspectJ XML开发

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

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

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
        
        ...
    </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 包中任意类的任意方法。

十七 Spring AOP:基于AspectJ注解开发

定义通知

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

<aop:aspect id="myAspect" ref="aBean">
    <!-- 前置通知 -->
    <aop:before pointcut-ref="myPointCut" method="..."/>
   
    <!-- 后置通知 -->
    <aop:after-returning pointcut-ref="myPointCut" method="..."/>
    <!-- 环绕通知 -->
    <aop:around pointcut-ref="myPointCut" method="..."/>
    <!-- 异常通知 -->
    <aop:after-throwing pointcut-ref="myPointCut" method="..."/>
    <!-- 最终通知 -->
    <aop:after pointcut-ref="myPointCut" method="..."/>
    .... 
</aop:aspect>

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

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

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

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

1)使用@Configuration和@EnableAspectJAutoProxy注解

@Configuration 
@EnableAspectJAutoProxy
public class Appconfig {
}

2)基于XML配置

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

aop:aspectj-autoproxy

定义切面@Aspect

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

package net.biancheng;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class AspectModule {
}

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

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

定义切入点@Pointcut

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

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

相当于以下代码

net.biancheng….*(…))” id=“myPointCut”/>

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

定义通知advice

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

@Before("myPointCut()")
public void beforeAdvice(){
    ...
}

示例

下面使用 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 类代码如下。

package net.biancheng;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class Logging {
    /**
     * 定义切入点
     */
    @Pointcut("execution(* net.biancheng.*.*(..))")
    private void selectAll() {
    }
    /**
     * 前置通知
     */
    @Before("selectAll()")
    public void beforeAdvice() {
        System.out.println("前置通知");
    }
    /**
     * 后置通知
     */
    @After("selectAll()")
    public void afterAdvice() {
        System.out.println("后置通知");
    }
    /**
     * 返回后通知
     */
    @AfterReturning(pointcut = "selectAll()", returning = "retVal")
    public void afterReturningAdvice(Object retVal) {
        System.out.println("返回值为:" + retVal.toString());
    }
    /**
     * 抛出异常通知
     */
    @AfterThrowing(pointcut = "selectAll()", throwing = "ex")
    public void afterThrowingAdvice(IllegalArgumentException ex) {
        System.out.println("这里的异常为:" + ex.toString());
    }
}

Man 类代码如下。

package net.biancheng;
public class Man {
    private String name;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public void throwException() {
        System.out.println("抛出异常");
        throw new IllegalArgumentException();
    }
}

Beans.xml 代码如下。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
    <aop:aspectj-autoproxy />
    <bean id="man" class="net.biancheng.Man">
        <property name="name" value="bianchengbang" />
        <property name="age" value="12" />
    </bean>
    <bean id="logging" class="net.biancheng.Logging" />
</beans>
        

MainApp 类代码如下。

package net.biancheng;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        Man man = (Man) context.getBean("man");
        man.getName();
        man.getAge();
        man.throwException();
    }
}

十八 Spring JdbcTemplate类

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

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

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

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http:/www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd"> 
   
    <!-- 配置数据源 --> 
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!--数据库驱动-->
        <property name="driverClassName" value="com.mysql.jdbc.Driver" /> 
        <!--连接数据库的url-->
        <property name= "url" value="jdbc:mysql://localhost/xx" />
        <!--连接数据库的用户名-->
        <property name="username" value="root" />
        <!--连接数据库的密码-->
        <property name="password" value="root" />
    </bean>
    <!--配置JDBC模板-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--默认必须使用数据源-->
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--配置注入类-->
    <bean id="xxx" class="xxx">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>
    ...
</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

示例

下面通过实例演示使用 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 类代码如下。

package net.biancheng;
public class User {
    private int id;
    private String name;
    private int age;
    public User() {
    }
    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    
    // 省略set和get方法
}

UserDao 代码如下。

package net.biancheng;
import java.util.List;
public interface UserDao {
    /**
     * 初始化User表
     */
    void createUserTable();
    /**
     * 保存用户
     */
    void saveUser(User user);
    /**
     * 查询用户
     */
    List<User> listUser();
}

UserDaoImpl 代码如下

package net.biancheng;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
public class UserDaoImpl implements UserDao {
    private JdbcTemplate jdbcTemplate;
    private UserDao userDao;
    public JdbcTemplate getJdbcTemplate() {
        return jdbcTemplate;
    }
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
    public UserDao getUserDao() {
        return userDao;
    }
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    public void setDataSource(DataSource datasource) {
        this.jdbcTemplate = new JdbcTemplate(datasource);
    }
    @Override
    public void createUserTable() {
        this.jdbcTemplate.execute("CREATE TABLE `user` (\r\n" + "  `id` int(11) NOT NULL AUTO_INCREMENT,\r\n"
                + "  `name` varchar(50) DEFAULT NULL,\r\n" + "  `age` int(11) DEFAULT NULL,\r\n"
                + "  PRIMARY KEY (`id`)\r\n" + ") ENGINE=MyISAM DEFAULT CHARSET=utf8;");
    }
    @Override
    public void saveUser(User user) {
        this.jdbcTemplate.update("INSERT INTO USER(NAME,age) VALUES (?,?)", user.getName(), user.getAge());
    }
    @Override
    public List<User> listUser() {
        List<User> users = this.jdbcTemplate.query("SELECT NAME,age FROM USER", new RowMapper<User>() {
            public User mapRow(ResultSet rs, int rowNum) throws SQLException {
                User user = new User();
                user.setName(rs.getString("name"));
                user.setAge(rs.getInt("age"));
                return user;
            }
        });
        return users;
    }
}

Beans.xml 代码如下。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context
     http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
    <!-- 配置数据源 -->
    <bean id="dataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!--数据库驱动 -->
        <property name="driverClassName"
            value="com.mysql.jdbc.Driver" />
        <!--连接数据库的url -->
        <property name="url" value="jdbc:mysql://localhost/test" />
        <!--连接数据库的用户名 -->
        <property name="username" value="root" />
        <!--连接数据库的密码 -->
        <property name="password" value="root" />
    </bean>
    <!--配置JDBC模板 -->
    <bean id="jdbcTemplate"
        class="org.springframework.jdbc.core.JdbcTemplate">
        <!--默认必须使用数据源 -->
        <property name="dataSource" ref="dataSource" />
    </bean>
    <bean id="userdao" class="net.biancheng.UserDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate" />
    </bean>
</beans>

MainApp 类代码如下。

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

我在这段时间是最迷茫的 我可以入是陷入了人生的低估也不为过 我感觉我自己无法完成我的梦想 无法完成他们的期望 可后来我发现我只是想的太多了 我们最终都会归于平凡 何必去争夺呢 加油吧 孩子 现在的努力是为了未来的你自己成长 未来的你一定会感谢现在的你2021.11.29

你可能感兴趣的:(java,spring)