Java Spring学习笔记

前置声明

本文绝大部分文案来自 C语言中文网

JUnit 单元测试文案来自 yiibai 网

Ⅰ、什么是 Java Spring

Spring 是分层的 JavaSE/EE full-stack 的轻量级开源框架,以 IoC (Inverse of Control, 控制反转) 和 AOP (Aspect Oriented Programming, 面向切面编程) 为内核,使用基本的 JavaBean 完成以前只能 EJB 完成的工作,取代了 EJB (Enterprise Java Beans) 的臃肿和低效的开发模式。

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

/**
	EJB (Enterprise Java Beans) 是基于分布式事务处理的企业级应用程序的组件。Sun公司发布的文档中对EJB的定义是:EJB是用于开发和部署多层结构的、分布式的、面向对象的Java应用系统的跨平台的构件体系结构。

	在开发分布式系统时, 采用EJB可以使得开发商业应用系统变得容易, 应用系统可以在一个支持EJB的环境中开发, 开发完之后部署在其它的EJB环境中, 随着需求的改变, 应用系统可以不加修改地迁移到其它功能更强、更复杂的服务器上。EJB在系统实现业务逻辑层里面负责表示程序的逻辑和提供访问数据库的接口。
*/
/**
	控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。
*/

1、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)声明式事务的支持

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

Ⅱ、Spring 的体系结构

Spring 框架采用分层架构,根据不同的功能被划分成了多个模块。

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

数据访问/集成层包括了 JDBC、ORM、JMS 和 Transactions 模块

  • JDBC 模块:提供了一个 JDBC 的抽象层,大幅度减少了再开发过程中对数据库操作的编码。
  • ORM模块:对流行的对象关系映射 API,包括 JPA、JDO、Hibernate 和 iBatis 提供了集成层。
  • OXM 模块:提供了一个支持对象/XML 映射的抽象层实现,如 JAXB、Castor、XMLBeans、JiBX 和 XStream。
  • JMS 模块:指 Java 消息服务,包含的功能为生产和消费的信息。
  • Transactions 事务模块:支持编程和声明式事务管理实现特殊接口类,并为所有的 POJO。
/**
	POJO(Plain Ordinary Java Object)简单的Java对象,实际就是普通JavaBeans,是为了避免和EJB混淆所创造的简称。
*/

2. Web 模块

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

  • Web 模块:提供了基本的 Web 开发集成特性,例如多文件上传功能、使用的 Servlet 监听器的 IoC 容器初始化以及 Web 应用上下文。
  • Servlet模块:包括 Spring 模型—视图—控制器(MVC)实现 Web 应用程序。
  • Struts 模块:包含支持类内的 Spring 应用程序,集成了经典的 Struts Web 层。
  • Portlet 模块:提供了在 Portlet 环境中使用 MV C实现,类似 Web-Servlet 模块的功能。

3. Core Container(核心容器)

Spring 的核心容器是其他模块建立的基础,由 Beans 模块、Core 核心模块、Context 上下文模块和 Expression Language 表达式语言模块组成,具体介绍如下。

  • Beans 模块:提供了 BeanFactory,是工厂模式的经典实现,Spring 将管理对象称为 Bean。
  • Core 核心模块:提供了 Spring 框架的基本组成部分,包括 IoC 和 DI 功能。
  • Context 上下文模块:建立在核心和 Beans 模块的基础之上,它是访问定义和配置任何对象的媒介。ApplicationContext 接口是上下文模块的焦点。
  • Expression Language 模块:是运行时查询和操作对象图的强大的表达式语言。

4. 其他模块

Spring的其他模块还有 AOP、Aspects、Instrumentation 以及 Test 模块,具体介绍如下。

  • AOP 模块:提供了面向切面编程实现,允许定义方法拦截器和切入点,将代码按照功能进行分离,以降低耦合性。
  • Aspects 模块:提供与 AspectJ 的集成,是一个功能强大且成熟的面向切面编程(AOP)框架。
  • Instrumentation 模块:提供了类工具的支持和类加载器的实现,可以在特定的应用服务器中使用。
  • Test 模块:支持 Spring 组件,使用 JUnit 或 TestNG 框架的测试。

Ⅲ、Spring 目录结构和基础 JAR 包介绍

Spring 下载链接:

在稳定版 Spring 3.2.13中,解压后的目录结构:

Spring目录结构

目录介绍:

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

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

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

读者可以通过网址 http://commons.apache.org/proper/commons-logging/download_logging.cgi 下载。该 JAR 包现在最新版本为 commons-logging.1.2,下载完成后,解压即可找到。

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

Ⅳ、Spring 框架中的Ioc容器:BeanFactory 和 ApplicationContext

1、容器

在Java当中,如果有一个类专门用来存放其它类的对象,这个类就叫做容器,或者就叫做集合,集合就是将若干性质相同或相近的类对象组合在一起而形成的一个整体

2、Java Spring 中的容器

在最开始的时候已经介绍过了 Spring IoC (Inversion of Control,控制反转),即指在程序开发中,实例的创建不再由调用者管理,而是由 Spring 容器进行创建。Spring 容器会负责控制程序之间的关系,而不是由程序代码直接控制,因此,控制权由程序代码转移到了 Spring 容器中,控制权发生了反转,这就是 Spring 的 IoC 思想。

Spring 提供了两种 IoC 容器,分别为 BeanFactory 和 ApplicationContext,接下来将针对这两种 IoC 容器进行详细讲解。

BeanFactory

BeanFactory 是基础类型的 IoC 容器,它由 org.springframework.beans.facytory.BeanFactory 接口定义,并提供了完整的 IoC 服务支持。简单来说,BeanFactory 就是一个管理 Bean 的工厂,它主要负责初始化各种 Bean,并调用它们的生命周期方法。

BeanFactory 接口有多个实现类,最常见的是 org.springframework.beans.factory.xml.XmlBeanFactory,它是根据 XML 配置文件中的定义装配 Bean 的。

创建 BeanFactory 实例时,需要提供 Spring 所管理容器的详细配置信息,这些信息通常采用 XML 文件形式管理。其加载配置信息的代码具体如下所示:

BeanFactory beanFactory = new XmlBeanFactory(new FileSystemResource("D://applicationContext.xml"));

ApplicationContext

ApplicationContext 是 BeanFactory 的子接口,也被称为应用上下文。该接口的全路径为 org.springframework.context.ApplicationContext,它不仅提供了 BeanFactory 的所有功能,还添加了对 i18n(国际化)、资源访问、事件传播等方面的良好支持。

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

1)ClassPathXmlApplicationContext

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

ApplicationContext applicationContext = new ClassPathXmlApplicationContext(String configLocation);

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

2)FileSystemXmlApplicationContext

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

ApplicationContext applicationContext = new FileSystemXmlApplicationContext(String configLocation);

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

在使用 Spring 框架时,可以通过实例化其中任何一个类创建 Spring 的 ApplicationContext 容器。

通常在 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。

Ⅴ、创建第一个 Java Spring 项目

1、导入框架支持

这里使用的是IDEA,首先新建一个空项目(或者选择以前存在的项目),在项目名右键并选中添加框架的支持

image-20210102131623168

然后在弹出的选择框里选择Spring,待所有文件下载完毕后,项目目录中会出现 lib 模块

image-20210102131825454

至此框架支持已经完成。

2、创建项目

1)在项目的 src 目录下创建一个名为 com.mengma.ioc 的包,然后在该包中创建一个名为 PersonDao 的接口,并在接口中添加一个 add() 方法,如下所示。

package com.mengma.ioc;
public interface PersonDao {
    public void add();
}

2)在 com.mengma.ioc 包下创建 PersonDao 的实现类 PersonDaoImpl,编辑后如下所示。

package com.mengma.ioc;
public class PersonDaoImpl implements PersonDao {
    private int num = 1;
    @Override
    public void add() {	// PersonDaoImpl 类实现了 PersonDao 接口中的 add() 方法,并且在方法调用时会执行输出语句。
        System.out.println("save()执行了...");
        num ++;
        System.out.println(num);
    }
}

3)在 src 目录下创建 Spring 的核心配置文件 applicationContext.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:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
    <!-- 由 Spring容器创建该类的实例对象 -->
    <bean id="personDao" class="com.mengma.ioc.PersonDaoImpl" />
</beans>

上述代码中,第 2~5 行代码是 Spring 的约束配置,第 7 行代码表示在 Spring 容器中创建一个 id 为 personDao 的 bean 实例,其中 id 表示文件中的唯一标识符,class 属性表示指定需要实例化 Bean 的实全限定类名(包名+类名)。

需要注意的是,Spring 的配置文件名称是可以自定义的,通常情况下,都会将配置文件命名为 applicationContext.xml(或 bean.xml)。

4)在 com.mengma.ioc 包下创建测试类 FirstTest,并在该类中添加一个名为 test1() 的方法,编辑后如下所示。

package com.mengma.ioc;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class FirstTest {
    @Test
    public void testl() {
        // 定义Spring配置文件的路径
        String xmlPath = "applicationContext.xml";
        // 初始化Spring容器,加载配置文件
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
                xmlPath);
        // 通过容器获取personDao实例
        PersonDao personDao = (PersonDao) applicationContext
                .getBean("personDao");
        // 调用 personDao 的 add ()方法
        personDao.add();
        
        // 重新获取一次PersonDao实例
        PersonDao personDao1 = (PersonDao) applicationContext.getBean("personDao");
        personDao1.add();
    }

}

上述代码中,首先定义了 Spring 配置文件的路径,然后创建 Spring 容器,接下来通过 Spring 容器获取了 personDao 实例,最后调用实例的 save() 方法。

5)运行并查看结果

image-20210102142251265

从输出结果中可以看出,程序已经成功输出了“save()执行了...”语句。在程序执行时,对象的创建并不是通过 new 一个类完成的,而是由 Spring 容器管理实现的。这就是 Spring IoC 容器思想的工作机制。

Ⅵ、Spring DI(依赖注入)的实现方式:属性注入和构造注入

依赖注入(Dependency Injection,DI)和控制反转含义相同,它们是从两个角度描述的同一个概念。

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

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

Spring 能有效地组织 J2EE 应用各层的对象。不管是控制层的 Action 对象,还是业务层的 Service 对象,还是持久层的 DAO 对象,都可在 Spring 的 管理下有机地协调、运行。Spring 将各层的对象以松耦合的方式组织在一起, Action 对象无须关心 Service 对象的具体实现,Service 对 象无须关心持久层对象的具体实现,各层对象的调用完全面向接口。当系统需要重构时,代码的改写量将大大减少。

上面所说的一切都得宜于 Spring 的核心机制,依赖注入。依赖注入让 bean 与 bean 之间以配置文件组织在一起,而不是以硬编码的方式耦合在一起。理解依赖注入

依赖注入(Dependency Injection)和控制反转(Inversion of Control)是同一个概念。具体含义是:当某个角色(可能是一个 Java 实例,调用者)需要另一个角色(另一个 Java 实例,被调用者)的协助时,在 传统的程序设计过程中,通常由调用者来创建被调用者的实例。但在 Spring 里,创建被调用者的工作不再由调用者来完成,因此称为控制反转;创建被调用者 实例的工作通常由 Spring 容器来完成,然后注入调用者,因此也称为依赖注入。

不管是依赖注入,还是控制反转,都说明 Spring 采用动态、灵活的方式来管理各种对象。对象与对象之间的具体实现互相透明。在理解依赖注入之前,看如下这个问题在各种社会形态里如何解决:一个人( Java 实例,调用者)需要一把斧子( Java 实例,被调用者)。

(1)原始社会里,几乎没有社会分工。需要斧子的人(调用者)只能自己去磨一把斧子(被调用者)。对应的情形为: Java 程序里的调用者自己创建被调用者。

(2)进入工业社会,工厂出现。斧子不再由普通人完成,而在工厂里被生产出来,此时需要斧子的人(调用者)找到工厂,购买斧子,无须关心斧子的制造过程。对应 Java 程序的简单工厂的设计模式。

(3)进入“按需分配”社会,需要斧子的人不需要找到工厂,坐在家里发出一个简单指令:需要斧子。斧子就自然出现在他面前。对应 Spring 的依赖注入。

第一种情况下,Java 实例的调用者创建被调用的 Java 实例,必然要求被调用的 Java 类出现在调用者的代码里。无法实现二者之间的松耦合。

第二种情况下,调用者无须关心被调用者具体实现过程,只需要找到符合某种标准(接口)的实例,即可使用。此时调用的代码面向接口编程,可以让调用者和被调用者解耦,这也是工厂模式大量使用的原因。但调用者需要自己定位工厂,调用者与特定工厂耦合在一起。

第三种情况下,调用者无须自己定位工厂,程序运行到需要被调用者时,系统自动提供被调用者实例。事实上,调用者和被调用者都处于 Spring 的管理下,二者之间的依赖关系由 Spring 提供。

依赖注入主要有两种实现方式,分别是属性 setter 注入和构造方法注入。具体介绍如下。

1、属性 setter 注入

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

2、构造方法注入

指 IoC 容器使用构造方法注入被依赖的实例。基于构造器的 DI 通过调用带参数的构造方法实现,每个参数代表一个依赖。

3、实例

下面通过属性 setter 注入的案例演示 Spring 容器是如何实现依赖注入的。具体步骤如下。

1) 创建 PersonService 接口

在 springDemo01 项目的 com.mengma.ioc 包下创建一个名为 PersonService 的接口,该接口中包含一个 addPerson() 方法,如下所示。

package com.mengma.ioc;

public interface PersonService {
    public void addPerson();
}

2)创建接口实现类 PersonServiceImpl

在 com.mengma.ioc 包下创建一个名为 PersonServiceImpl 的类,该类实现了 PersonService 接口,如下所示。

package com.mengma.ioc;

public class PersonServiceImpl implements PersonService {

    // 定义接口声明
    private PersonDao personDao;	// 这里声明了一个类型为 PersonDao 的变量,注意后面会用到

    // 提供set()方法,用于依赖注入,setter() 方法用于设值注入的依赖注入方式,用 setter() 方法的参数就是设值注入的对象
    public void setPersonDao(PersonDao personDao) {
        this.personDao = personDao;
    }

    // 实现PersonService接口的方法
    @Override
    public void addPerson() {
        personDao.add(); // 调用PersonDao中的add()方法
        System.out.println("addPerson()执行了...");
    }

}

上述代码中,首先声明了 personDao 对象,并为其添加 setter 方法,用于依赖注入,然后实现了 PersonDao 接口的 addPerson() 方法,并在方法中调用 add() 方法和输出一条语句。

3)在 applicationContext.xml 中添加配置信息(注意是在前面的基础上添加)

在 applicationContext.xml 配置文件中添加一个 元素,用于实例化 PersonServiceImpl 类,并将 personDao 的实例注入到 personService 中,其实现代码如下所示:

<!-- 关于 personDao 的 Bean 实例已经在以前的配置文件基础上创建好了 -->
<bean id="personService" class="com.mengma.ioc.PersonServiceImpl">
    <!-- 将personDao实例注入personService实例中 -->
    <property name="personDao" ref="personDao"/>	
    <!-- property 属性用来指定需要容器注入的属性,这里的 personDao 属性需要的是设值注入,所以对应的被注入类 PersonServiceImpl 需要 setter 方法,这里的 ref 属性则是指定对 Bean 工厂的 Bean 实例的引用 -->
</bean>

从这里可以看到,Spring 框架将各个实例之间进行了松耦合,bean 与 bean 实例之间的依赖关系并不是放在具体的实现类当中,而是通过配置文件的指定,让 Spring 精确的为每个 bean 注入属性,添加对另一个 bean 实例的依赖。正因如此,bean 属性的 class 参数不能仅仅是接口,而一定要是一个实现类,即能够实例化。

Spring 会自动接管每个 bean 定义中的 property 元素定义。Spring会在执行无参数的构造器后、创建默认的bean实例后,调用对应 的setter方法为程序注入属性值。property定义的属性值将不再由该bean来主动创建、管理,而改为被动接收Spring的注入。

每个bean的id属性是该bean的惟一标识,程序通过id属性访问bean,bean与bean的依赖关系也通过id属性完成。

当需要修改 PersonDao 类相关信息时,则无需修改与其有联系的 PersonServiceImpl 类 。如:

<!-- 如果新建了一个 bean 实例对象,名为 personDao1 -->
<bean id="personService" class="com.mengma.ioc.PersonServiceImpl">

    <property name="personDao" ref="personDao1"/>
    <!-- 只需要修改注入的 ref 属性值就可以 -->
</bean>

PersonServiceImpl 与 PersonDao 之间没有任何的代码耦合关系,只有由 Spring 管理的 bean 之间的依赖关系。

4)编写测试方法

在 FirstTest 类中创建一个名为 test2() 的方法,编辑后如下所示:

@Test
public void test2() {
    // 定义Spring配置文件的路径
    String xmlPath = "applicationContext.xml";
    // 初始化Spring容器,加载配置文件
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
            xmlPath);
    // 通过容器获取personService实例
    PersonService personService = (PersonService) applicationContext
            .getBean("personService");
    // 调用personService的addPerson()方法
    personService.addPerson();
}

5)运行查看

运行结果

从输出结果中可以看出,使用 Spring 容器获取 userService 的实例后,调用了该实例的 addPerson() 方法,在该方法中又调用了 PersonDao 实现类中的 add() 方法,并输出了结果。这就是 Spring 容器属性 setter 注入的方式,也是实际开发中较为常用的一种方式。

Ⅶ、Spring Bean的配置及常用属性

作为 Spring 核心机制的依赖注入,改变了传统的编程习惯,对组件的实例化不再由应用程序完成,转而交由 Spring 容器完成,在需要时注入应用程序中,从而对组件之间依赖关系进行了解耦。这一切都离不开 Spring 配置文件中使用的 元素。

Spring 容器可以被看作一个大工厂,而 Spring 容器中的 Bean 就相当于该工厂的产品。如果希望这个大工厂能够生产和管理 Bean,这时则需要告诉容器需要哪些 Bean,以及需要以何种方式将这些 Bean 装配到一起。

Spring 配置文件支持两种不同的格式,分别是 XML 文件格式和 Properties 文件格式。

通常情况下,Spring 会以 XML 文件格式作为 Spring 的配置文件,这种配置方式通过 XML 文件注册并管理 Bean 之间的依赖关系。

XML 格式配置文件的根元素是 ,该元素包含了多个 子元素,每一个 子元素定义了一个 Bean,并描述了该 Bean 如何被装配到 Spring 容器中。

定义 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" xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
    <!-- 使用id属性定义person1,其对应的实现类为com.mengma.person1 -->
    <bean id="person1" class="com.mengma.damain.Person1" />
    <!--使用name属性定义person2,其对应的实现类为com.mengma.domain.Person2-->
    <bean name="Person2" class="com.mengma.domain.Person2"/>
</beans>

在上述代码中,分别使用 id 和 name 属性定义了两个 Bean,并使用 class 元素指定了 Bean 对应的实现类。

元素中包含很多属性,其常用属性如下表所示。

属性名称 描述
id 是一个 Bean 的唯一标识符,Spring 容器对 Bean 的配置和管理都通过该属性完成
name Spring 容器同样可以通过此属性对容器中的 Bean 进行配置和管理,name 属性中可以为 Bean 指定多个名称,每个名称之间用逗号或分号隔开
class 该属性指定了 Bean 的具体实现类,它必须是一个完整的类名,使用类的全限定名
scope 用于设定 Bean 实例的作用域,其属性值有 singleton(单例)、prototype(原型)、request、session 和 global Session。其默认值是 singleton
constructor-arg 元素的子元素,可以使用此元素传入构造参数进行实例化。该元素的 index 属性指定构造参数的序号(从 0 开始),type 属性指定构造参数的类型
property 元素的子元素,用于调用 Bean 实例中的 Set 方法完成属性赋值,从而完成依赖注入。该元素的 name 属性指定 Bean 实例中的相应属性名
ref 等元素的子元素,该元素中的 bean 属性用于指定对 Bean 工厂中某个 Bean 实例的引用
value 等元素的子元素,用于直接指定一个常量值
list 用于封装 List 或数组类型的依赖注入
set 用于封装 Set 类型属性的依赖注入
map 用于封装 Map 类型属性的依赖注入
entry 元素的子元素,用于设置一个键值对。其 key 属性指定字符串类型的键值,ref 或 value 子元素指定其值

Ⅷ、Spring实例化Bean的三种方法

在面向对象的程序中,要想调用某个类的成员方法,就需要先实例化该类的对象。在 Spring 中,实例化 Bean 有三种方式,分别是构造器实例化、静态工厂方式实例化和实例工厂方式实例化。本节将针对这三种方式分别进行讲解。

1、构造器实例化

构造器实例化是指 Spring 容器通过 Bean 对应的类中默认的构造函数实例化 Bean。下面通过案例演示如何使用构造器实例化 Bean。

1)创建项目并添加依赖

2)创建实体类

在项目的 src 目录下创建一个名为 com.mengma.instance.constructor 的包,在该包下创建一个实体类 Person1,如下所示。

package com.mengma.instance.constructor;
public class Person1 {
}

4)创建 Spring 配置文件

在 com.mengma.instance.constructor 包下创建 Spring 的配置文件 applicationContext.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:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
    <bean id="person1" class="com.mengma.instance.constructor.Person1" />	<!-- 建立容器声明 -->
</beans>

4)创建测试类

在 com.mengma.instance.constructor 包下创建一个名为 InstanceTest1 的测试类,编辑后如下所示。

package com.mengma.instance.constructor;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class InstanceTest1 {
    public static void main(String[] args) {
        // 定义Spring配置文件的路径
        String xmlPath = "com/mengma/instance/constructor/ApplicationContext.xml";
        // 初始化Spring容器,加载配置文件,并对bean进行实例化
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
                xmlPath);
        // 通过容器获取id为person1的实例
        System.out.println(applicationContext.getBean("person1"));
    }
}

2、静态工厂方式实例化

在 Spring 中,也可以使用静态工厂的方式实例化 Bean。此种方式需要提供一个静态工厂方法创建 Bean 的实例。下面通过案例演示如何使用静态工厂方式实例化 Bean。

1)创建实体类

在项目的 src 目录下创建一个名为 com.mengma.instance.static_factory 的包,并在该包下创建一个实体类 Person2,该类与 Person1 相同,不需要添加任何成员。

2)创建静态工厂类

在 com.mengma.instance.static_factory 包下创建一个名为 MyBeanFactory 的类,并在该类中创建一个名为 createBean() 的静态方法,用于创建 Bean 的实例,如下所示。

package com.mengma.instance.static_factory;
public class MyBeanFactory {
    // 创建Bean实例的静态工厂方法
    public static Person2 createBean() {
        return new Person2();
    }
}

3)创建 Spring 配置文件

在 com.mengma.instance.static_factory 包下创建 Spring 的配置文件 applicationContext.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:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
    
    <!-- class 属性指定了其对应的工厂实现类为 MyBeanFactory,而 factory-method 属性用于告诉 Spring 容器调用工厂类中的 createBean() 方法获取 Bean 的实例。 -->
    <bean id="person2" class="com.mengma.instance.static_factory.MyBeanFactory"
        factory-method="createBean" />
</beans>

4)创建测试类

在 com.mengma.instance.static_factory 包下创建一个名为 InstanceTest2 的测试类,编辑后如下所示。

package com.mengma.instance.static_factory;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class InstanceTest2 {
    public static void main(String[] args) {
        // 定义Spring配置文件的路径
        String xmlPath = "com/mengma/instance/static_factory/applicationContext.xml"; // 初始化Spring容器,加载配置文件,并对bean进行实例化
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
                xmlPath);
        // 通过容器获取id为person2实例
        System.out.println(applicationContext.getBean("person2"));
    }
}

3、实例工厂方式实例化

在 Spring 中,还有一种实例化 Bean 的方式就是采用实例工厂。在这种方式中,工厂类不再使用静态方法创建 Bean 的实例,而是直接在成员方法中创建 Bean 的实例。

同时,在配置文件中,需要实例化的 Bean 也不是通过 class 属性直接指向其实例化的类,而是通过 factory-bean 属性配置一个实例工厂,然后使用 factory-method 属性确定使用工厂中的哪个方法。下面通过案例演示实例工厂方式的使用。

1)创建实体类

在项目的 src 目录下创建一个名为 com.mengma.instance.factory 的包,在该包下创建一个 Person3 类,该类与 Person1 类相同,不需要添加任何成员。

2)创建实例工厂类

在 com.mengma.instance.factory 包下创建一个名为 MyBeanFactory 的类,编辑后如下所示。

package com.mengma.instance.factory;
public class MyBeanFactory {
    public MyBeanFactory() {
        System.out.println("person3工厂实例化中");
    }
    // 创建Bean的方法
    public Person3 createBean() {
        return new Person3();
    }
}

3)创建 Spring 配置文件

在 com.mengma.instance.factory 包下创建 Spring 的配置文件 applicationContext.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:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
    <!-- 配置实例工厂 -->
    <bean id="myBeanFactory" class="com.mengma.instance.factory.MyBeanFactory" />
    <!-- factory-bean属性指定一个实例工厂,factory-method属性确定使用工厂中的哪个方法 -->
    <bean id="person3" factory-bean="myBeanFactory" factory-method="createBean" />
</beans>

4)创建测试类

在 com.mengma.instance.factory 包下创建一个名为 InstanceTest3 的测试类,编辑后如下所示。

package com.mengma.instance.factory;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class InstanceTest3 {
    @Test
    public void test() {
        // 定义Spring配置文件的路径
        String xmlPath = "com/mengma/instance/factory/applicationContext.xml"; // 初始化Spring容器,加载配置文件,并对bean进行实例化
        // 初始化Spring容器,加载配置文件,并对bean进行实例化
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
                xmlPath);
        // 通过容器获取id为person3实例
        System.out.println(applicationContext.getBean("person3"));
    }
}

Ⅸ、Spring中Bean的作用域

本节先简单介绍了 Spring 中 bean 的 5 种作用域,然后详细介绍 singleton 和 prototype 这两种最常用的作用域。

1、作用域的种类

Spring 容器在初始化一个 Bean 的实例时,同时会指定该实例的作用域。Spring3 为 Bean 定义了五种作用域,具体如下。

1)singleton

单例模式,使用 singleton 定义的 Bean 在 Spring 容器中只有一个实例,这也是 Bean 默认的作用域。

2)prototype

原型模式,每次通过 Spring 容器获取 prototype 定义的 Bean 时,容器都将创建一个新的 Bean 实例。

3)request

在一次 HTTP 请求中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Request 内有效。

4)session

在一次 HTTP Session 中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Session 内有效。

5)global Session

在一个全局的 HTTP Session 中,容器会返回该 Bean 的同一个实例。该作用域仅在使用 portlet context 时有效。

在上述五种作用域中,singleton 和 prototype 是最常用的两种,接下来将对这两种作用域进行详细讲解。

2、singleton 作用域

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

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

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

<bean id="person" class="com.mengma.scope.Person" scope="singleton"/>

获取并输出 id 为 person 的实例:

 System.out.println(applicationContext.getBean("person"));
 System.out.println(applicationContext.getBean("person"));

image-20210102153029083

3、prototype 作用域

使用 prototype 作用域的 Bean 会在每次请求该 Bean 时都会创建一个新的 Bean 实例。因此对需要保持会话状态的 Bean(如 Struts2 的 Action 类)应该使用 prototype 作用域。

在 Spring 配置文件中,要将 Bean 定义为 prototype 作用域,只需将 元素的 scope 属性值定义成 prototype,其示例代码如下所示:

<bean id="person" class="com.mengma.scope.Person" scope="prototype"/>

再次输出:

image-20210102153132317

Ⅹ、Spring Bean的生命周期

Spring 容器可以管理 singleton 作用域 Bean 的生命周期,在此作用域下,Spring 能够精确地知道该 Bean 何时被创建,何时初始化完成,以及何时被销毁。

而对于 prototype 作用域的 Bean,Spring 只负责创建,当容器创建了 Bean 的实例后,Bean 的实例就交给客户端代码管理,Spring 容器将不再跟踪其生命周期。每次客户端请求 prototype 作用域的 Bean 时,Spring 容器都会创建一个新的实例,并且不会管那些被配置成 prototype 作用域的 Bean 的生命周期。

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

在 Spring 中,Bean 的生命周期是一个很复杂的执行过程,我们可以利用 Spring 提供的方法定制 Bean 的创建过程。

当一个 Bean 被加载到 Spring 容器时,它就具有了生命,而 Spring 容器在保证一个 Bean 能够使用之前,会进行很多工作。Spring 容器中 Bean 的生命周期流程如图所示。

Bean的生命周期

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

1)根据配置情况调用 Bean 构造方法或工厂方法实例化 Bean。

2)利用依赖注入完成 Bean 中所有属性值的配置注入。

3)如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前 Bean 的 id 值。

4)如果 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂实例的引用。

5)如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。

6)如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的预初始化方法 postProcessBeforeInitialzation() 对 Bean 进行加工操作,此处非常重要,Spring 的 AOP 就是利用它实现的。

7)如果 Bean 实现了 InitializingBean 接口,则 Spring 将调用 afterPropertiesSet() 方法。

8)如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。

9)如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的初始化方法 postProcessAfterInitialization()。此时,Bean 已经可以被应用系统使用了。

10)如果在 中指定了该 Bean 的作用范围为 scope="singleton",则将该 Bean 放入 Spring IoC 的缓存池中,将触发 Spring 对该 Bean 的生命周期管理;如果在 中指定了该 Bean 的作用范围为 scope="prototype",则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该 Bean。

11)如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法将 Spring 中的 Bean 销毁;如果在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁。

Spring 为 Bean 提供了细致全面的生命周期过程,通过实现特定的接口或 的属性设置,都可以对 Bean 的生命周期过程产生影响。虽然可以随意配置 的属性,但是建议不要过多地使用 Bean 实现接口,因为这样会导致代码和 Spring 的聚合过于紧密。

ⅩⅠ、Spring基于XML装配Bean

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

Spring 基于 XML 的装配通常采用两种实现方式,即设值注入(Setter Injection)和构造注入(Constructor Injection)。本节将讲解如何在 XML 配置文件中使用这两种注入方式。

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

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

使用设值注入时,在 Spring 配置文件中,需要使用 元素的子元素 元素为每个属性注入值。而使用构造注入时,在配置文件中,主要使用 标签定义构造方法的参数,可以使用其 value 属性(或子元素)设置该参数的值。下面通过案例演示基于 XML 方式的 Bean 的装配。

1)创建 Person 类

在项目 springDemo02 中的 src 目录下,创建一个名称为 com.mengma.assembly 的包,在该包下创建一个 Person 类,如下所示。

package com.mengma.assembly;

public class Person {
    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;
    }

    // 重写toString()方法
    public String toString() {
        return "Person[name=" + name + ",age=" + age + "]";
    }

    // 默认无参的构造方法
    public Person() {
        super();
    }

    // 有参的构造方法
    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
}

上述代码中,定义了 name 和 age 两个属性,并为其提供了 getter 和 setter 方法,由于要使用构造注入,所以需要提供有参的构造方法。为了能更清楚地看到输出结果,这里还重写了 toString() 方法。

2)创建 Spring 配置文件

在 com.mengma.assembly 包下创建一个名为 applicationContext.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:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
    <!-- 使用设值注入方式装配Person实例 -->
    <bean id="person1" class="com.mengma.assembly.Person">
        <property name="name" value="zhangsan" />
        <property name="age" value="20" />
    </bean>
    <!-- 使用构造方法装配Person实例 -->
    <bean id="person2" class="com.mengma.assembly.Person">
        <constructor-arg index="0" value="lisi" />
        <constructor-arg index="1" value="21" />
    </bean>
</beans>

上述代码中,首先使用了设值注入方式装配 Person 类的实例,其中 子元素用于调用 Bean 实例中的 setXxx() 方法完成属性赋值。然后使用了构造方式装配了 Person 类的实例,其中 元素用于定义构造方法的参数,其属性 index 表示其索引(从 0 开始),value 属性用于设置注入的值。

3)创建测试类

在 com.mengma.assembly 包下创建一个名称为 XmlBeanAssemblyTest 的测试类,编辑后如下所示。

package com.mengma.assembly;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class XmlBeanAssemblyTest {
    @Test
    public void test() {
        // 定义Spring配置文件路径
        String xmlPath = "com/mengma/assembly/applicationContext.xml";
        // 初始化Spring容器,加载配置文件,并对bean进行实例化
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
                xmlPath);
        // 设值方式输出结果
        System.out.println(applicationContext.getBean("person1"));
        // 构造方式输出结果
        System.out.println(applicationContext.getBean("person2"));
    }
}

上述代码中,分别获取并输出了 id 为 person1 和 person2 的实例。

4)运行项目并查看结果

image-20210102160034739

ⅩⅡ、Spring基于Annotation装配Bean

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

Java 从 JDK 5.0 以后,提供了 Annotation(注解)功能,Spring 也提供了对 Annotation 技术的全面支持。Spring3 中定义了一系列的 Annotation(注解),常用的注解如下。

1)@Component

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

2)@Repository

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

3)@Service

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

4)@Controller

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

5)@Autowired

用于对 Bean 的属性变量、属性的 Set 方法及构造函数进行标注,配合对应的注解处理器完成 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 注解的参数指定。

示例:

1)创建 DAO 层接口

在 src 目录下创建一个名为 com.mengma.annotation 的包,在该包下创建一个名为 PersonDao 的接口,并添加一个 add() 方法,如下所示。

package com.mengma.annotation;
public interface PersonDao {
    public void add();
}

2)创建 DAO 层接口的实现类

在 com.mengma.annotation 包下创建 PersonDao 接口的实现类 PersonDaoImpl,编辑后如下所示。

package com.mengma.annotation;
import org.springframework.stereotype.Repository;
@Repository("personDao")
public class PersonDaoImpl implements PersonDao {
    @Override
    public void add() {
        System.out.println("Dao层的add()方法执行了...");
    }
}

上述代码中,首先使用 @Repository 注解将 PersonDaoImpl 类标识为 Spring 中的 Bean,其写法相当于配置文件中 <bean id="personDao"class="com.mengma.annotation.PersonDaoImpl"/> 的书写。然后在 add() 方法中输出一句话,用于验证是否成功调用了该方法。

3)创建 Service 层接口

在 com.mengma.annotation 包下创建一个名为 PersonService 的接口,并添加一个 add() 方法,如下所示。

package com.mengma.annotation;
public interface PersonService {
    public void add();
}

4)创建 Service 层接口的实现类

在 com.mengma.annotation 包下创建 PersonService 接口的实现类 PersonServiceImpl,编辑后如下所示。

package com.mengma.annotation;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
@Service("personService")
public class PersonServiceImpl implements PersonService {
    @Resource(name = "personDao")
    private PersonDao personDao;
    public PersonDao getPersonDao() {
        return personDao;
    }
    @Override
    public void add() {
        personDao.add();// 调用personDao中的add()方法
        System.out.println("Service层的add()方法执行了...");
    }
}

上述代码中,首先使用 @Service 注解将 PersonServiceImpl 类标识为 Spring 中的 Bean,其写法相当于配置文件中 <bean id="personService"class="com.mengma.annotation.PersonServiceImpl"/> 的书写。

然后使用 @Resource 注解标注在属性 personDao 上(也可标注在 personDao 的 setPersonDao() 方法上),这相当于配置文件中 <property name="personDao"ref="personDao"/> 的写法。最后在该类的 add() 方法中调用 personDao 中的 add() 方法,并输出一句话。

5)创建 Action

在 com.mengma.annotation 包下创建一个名为 PersonAction 的类,编辑后如下所示。

package com.mengma.annotation;
import javax.annotation.Resource;
import org.springframework.stereotype.Controller;
@Controller("personAction")
public class PersonAction {
    @Resource(name = "personService")
    private PersonService personService;
    public PersonService getPersonService() {
        return personService;
    }
    public void add() {
        personService.add(); // 调用personService中的add()方法
        System.out.println("Action层的add()方法执行了...");
    }
}

上述代码中,首先使用 @Controller 注解标注 PersonAction 类,其写法相当于在配置文件中编写 <bean id="personAction"class="com.mengma.annotation.PersonAction"/>。

然后使用了 @Resource 注解标注在 personService 上,这相当于在配置文件内编写 <property name="personService"ref="personService"/>。

最后在其 add() 方法中调用了 personService 中的 add() 方法,并输出一句话。

6)创建 Spring 配置文件

在 com.mengma.annotation 包下创建一个名为 applicationContext.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"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
            http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
    <!--使用context命名空间,通知spring扫描指定目录,进行注解的解析-->
    <context:component-scan base-package="com.mengma.annotation"/>
</beans>

与之前的配置文件相比,上述代码的元素中增加了第 7 行、第 15 行和第 16 行中包含有 context 的代码,然后在第 18 行代码中,使用 context 命名空间的 component-scan 元素进行注解的扫描,其 base-package 属性用于通知 spring 所需要扫描的目录。

7)创建测试类

在 com.mengma.annotation 包下创建一个名为 AnnotationTest 的测试类,编辑后如下所示。

package com.mengma.annotation;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AnnotationTest {
    public static void main(String[] args) {
        String xmlPath = "com\\mengma\\annotation\\applicationContext.xml";
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
        PersonAction personAction = (PersonAction) applicationContext.getBean("personAction");
        personAction.add();
    }
}

8)运行并查看结果

image-20210104142414391

ⅩⅢ、Spring自动装配Bean

除了使用 XML(配置文件) 和 Annotation(注解@) 的方式装配 Bean 以外,还有一种常用的装配方式——自动装配。自动装配就是指 Spring 容器可以自动装配(autowire)相互协作的 Bean 之间的关联关系,将一个 Bean 注入其他 Bean 的 Property 中。

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

名称 说明
byName 根据 Property 的 name 自动装配,如果一个 Bean 的 name 和另一个 Bean 中的 Property 的 name 相同,则自动装配这个 Bean 到 Property 中。
byType 根据 Property 的数据类型(Type)自动装配,如果一个 Bean 的数据类型兼容另一个 Bean 中 Property 的数据类型,则自动装配。
constructor 根据构造方法的参数的数据类型,进行 byType 模式的自动装配。
autodetect 如果发现默认的构造方法,则用 constructor 模式,否则用 byType 模式。
no 默认情况下,不使用自动装配,Bean 依赖必须通过 ref 元素定义。

实例:

在创建了相应的类并且创建相应的 setter 方法过后,如果要使用 Spring 的自动装配,只需要改动配置文件(删除注解),以 com.mengma.annotation 项目为例,将 applicationContext.xml修改为:

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

    <bean id = "personDao" class = "com.mengma.annotation.PersonDaoImpl"/>
    <bean id = "personService" class = "com.mengma.annotation.PersonServiceImpl" autowire = "byName"/> 
    <bean id = "personAction" class = "com.mengma.annotation.PersonAction" autowire = "byName"/>
</beans>

在上述配置文件中,用于配置 personService 和 personAction 的 元素中除了 id 和 class 属性以外,还增加了 autowire 属性,并将其属性值设置为 byName(按属性名称自动装配)。

默认情况下,配置文件中需要通过 ref 装配 Bean,但设置了 autowire="byName",Spring 会在配置文件中自动寻找与属性名字 personDao 相同的 ,找到后,通过调用 setPersonDao(PersonDao personDao)方法将 id 为 personDao 的 Bean 注入 id 为 personService 的 Bean 中,这时就不需要通过 ref 装配了。

image-20210104150035218

ⅩⅣ、Spring AOP(面向切面编程)是什么?

面向切面编程(AOP)和面向对象编程(OOP)类似,也是一种编程模式。Spring AOP 是基于 AOP 编程模式的一个框架,它的使用有效减少了系统间的重复代码,达到了模块间的松耦合目的。

AOP 的全称是“Aspect Oriented Programming”,即面向切面编程,它将业务逻辑的各个部分进行隔离,使开发人员在编写业务逻辑时可以专心于核心业务,从而提高了开发效率。

AOP 采取横向抽取机制,取代了传统纵向继承体系的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。

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

Spring AOP 使用纯 Java 实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类植入增强的代码。

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

为了更好地理解 AOP,就需要对 AOP 的相关术语有一些了解,这些专业术语主要包含 Joinpoint、Pointcut、Advice、Target、Weaving、Proxy 和 Aspect,它们的含义如下表所示。

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

ⅩⅤ、代理模式

1、概念

为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不想或者不能直接引用另外一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

2、代理模式

1)远程代理(Remote Proxy)

为一个位于不同的地址空间的对象提供一个本地的代理对象。这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中。也即为不同地址空间提供局部的代表。

2)虚拟代理(Virtual Proxy)

根据需要创建开销很大的对象。如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。 (将目的对象添加到代理对象的方法中,当方法被调用的时候才会创建目的对象的实例)

3)保护代理(Protection Proxy)

控制对原始对象的访问。保护代理用于对象应该有不同的访问权限的时候。(添加判断,当条件满足时才会创建目的对象的实例)

4)智能指引(Smart Reference)

取代了简单的指针,它在访问对象时执行一些附加操作。

5)Copy-on-Write代理

它是虚拟代理的一种,把复制(克隆)操作延迟到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个开销较大的操作,Copy-on-Write代理可以让这个操作延迟,只有对象被用到的时候才被克隆。

ⅩⅥ、Spring JDK动态代理(附带实例)

Proxy提供了创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理类的超类。

为某个接口创建代理Foo

  InvocationHandler handler = new MyInvocationHandler(...);
     Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);
     Foo f = (Foo) proxyClass.getConstructor(InvocationHandler.class).
                     newInstance(handler); 

或更简单地:

  Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
                                          new Class<?>[] { Foo.class },
                                          handler); 

动态代理类 (以下简称为代理类 )是一个实现在类创建时在运行时指定的接口列表的类,具有如下所述的行为。 代理接口是由代理类实现的接口。 代理实例是代理类的一个实例。 每个代理实例都有一个关联的调用处理程序对象,它实现了接口InvocationHandler 。 通过其代理接口之一的代理实例上的方法调用将被分派到实例调用处理程序的invoke方法,传递代理实例, java.lang.reflect.Method被调用方法的java.lang.reflect.Method对象以及包含参数的类型Object Object的数组。 调用处理程序适当地处理编码方法调用,并且返回的结果将作为方法在代理实例上调用的结果返回。

代理类具有以下属性:

  • 代理类是公共的,最终的,而不是抽象的,如果所有代理接口都是公共的。
  • 如果任何代理接口是非公开的,代理类是非公开的,最终的,而不是抽象的
  • 代理类的不合格名称未指定。 然而,以字符串"$Proxy"开头的类名空间应该保留给代理类。
  • 一个代理类扩展了java.lang.reflect.Proxy
  • 代理类完全按照相同的顺序实现其创建时指定的接口。
  • 如果一个代理类实现一个非公共接口,那么它将被定义在与该接口相同的包中。 否则,代理类的包也是未指定的。 请注意,程序包密封不会阻止在运行时在特定程序包中成功定义代理类,并且类也不会由同一类加载器定义,并且与特定签名者具有相同的包。
  • 由于代理类实现了在其创建时指定的所有接口, getInterfaces在其对象上调用getInterfaces将返回一个包含相同列表接口的数组(按其创建时指定的顺序),在其对象上调用getMethods将返回一个数组的方法对象,其中包括这些接口中的所有方法,并调用getMethod将在代理接口中找到可以预期的方法。
  • Proxy.isProxyClass方法将返回true,如果它通过代理类 - 由Proxy.getProxyClass返回的类或由Proxy.newProxyInstance返回的对象的类 - 否则为false。
  • 所述java.security.ProtectionDomain代理类的是相同由引导类装载程序装载系统类,如java.lang.Object ,因为是由受信任的系统代码生成代理类的代码。 此保护域通常将被授予java.security.AllPermission
  • 每个代理类有一个公共构造一个参数,该接口的实现InvocationHandler ,设置调用处理程序的代理实例。 而不必使用反射API来访问公共构造函数,也可以通过调用Proxy.newProxyInstance方法来创建代理实例,该方法将调用Proxy.getProxyClass的操作与调用处理程序一起调用构造函数。

代理实例具有以下属性:

  • 给定代理实例proxy和其代理类Foo ,以下表达式将返回true:

       proxy instanceof Foo 
    

    并且以下演员操作将会成功(而不是投掷一个ClassCastException ):

       (Foo) proxy 
    
  • 每个代理实例都有一个关联的调用处理程序,它被传递给它的构造函数。 静态Proxy.getInvocationHandler方法将返回与作为其参数传递的代理实例关联的调用处理程序。

  • 代理实例上的接口方法调用将被编码并分派到调用处理程序的invoke方法,如该方法的文档所述。

  • hashCodeequals在代理实例上的toString中声明的java.lang.ObjecttoStringtoStringtoString方法将被编码并分派到调用处理程序的invoke方法,方法与接口方法调用被编码和调度相同。 传递给invoke方法对象的声明类将为java.lang.Object 。 从java.lang.Object的代理实例的其他公共方法不会被代理类覆盖,因此这些方法的调用与java.lang.Object

多代理接口中复制的方法

当代理类的两个或多个接口包含具有相同名称和参数签名的方法时,代理类接口的顺序变得重要。 当在代理实例上调用这种重复方法时,传递给调用处理程序的方法对象不一定是其声明类可以通过调用代理方法的接口的引用类型进行分配的对象。 存在此限制,因为生成的代理类中的相应方法实现无法确定其调用的接口。 因此,当在代理实例上调用重复的方法时,代理类的方法列表中包含方法(直接或通过超级接口继承)的最重要的接口中的方法的Method对象被传递给调用处理程序的invoke方法,而不管方法调用发生的引用类型。

如果代理接口包含具有相同的名称和参数签名的方法hashCodeequals ,或toString的方法java.lang.Object ,当这种方法在代理实例调用时, 方法传递到调用处理程序对象将有java.lang.Object为申报班 换句话说, java.lang.Object的公共非最终方法java.lang.Object上先于所有代理接口,以确定哪个方法对象传递给调用处理程序。

还要注意,当将一个重复的方法分派到调用处理程序时, invoke方法可能只会将可分配给所有可以调用的所有代理接口中的方法的throws子句中的一种异常类型的检查异常类型抛出通过。 如果invoke方法抛出经过检查的异常是不能分配给任何通过它可以通过调用代理接口中的方法声明的异常类型,那么选中UndeclaredThrowableException将通过代理实例调用抛出。 此限制意味着,并非所有通过调用返回的异常类型getExceptionTypes方法传递给对象invoke方法一定可以成功地抛出invoke方法。

实例

1)创建项目

2)创建接口 CustomerDao

在项目的 src 目录下创建一个名为 com.mengma.dao 的包,在该包下创建一个 CustomerDao 接口,编辑后如下所示。

package com.mengma.dao;
public interface CustomerDao {
    public void add(); // 添加
    public void update(); // 修改
    public void delete(); // 删除
    public void find(); // 查询
}

3)创建实现类 CustomerDaoImpl

在 com.mengma.dao 包下创建 CustomerDao 接口的实现类 CustomerDaoImpl,并实现该接口中的所有方法,如下所示。

package com.mengma.dao;
public class CustomerDaoImpl implements CustomerDao {
    @Override
    public void add() {
        System.out.println("添加客户...");
    }
    @Override
    public void update() {
        System.out.println("修改客户...");
    }
    @Override
    public void delete() {
        System.out.println("删除客户...");
    }
    @Override
    public void find() {
        System.out.println("修改客户...");
    }
}

4)创建切面类 MyAspect

在 src 目录下,创建一个名为 com.mengma.jdk 的包,在该包下创建一个切面类 MyAspect,编辑后如下所示。

package com.mengma.jdk;

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

上述代码中,在切面中定义了两个增强的方法,分别为 myBefore() 方法和 myAfter() 方法,用于对目标类(CustomerDaoImpl)进行增强。

5)创建代理类 MyBeanFactory

在 com.mengma.jdk 包下创建一个名为 MyBeanFactory 的类,在该类中使用 java.lang.reflect.Proxy 实现 JDK 动态代理,如下所示。

package com.mengma.jdk;

import com.mengma.dao.CustomerDao;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class MyBeanFactory {

    // 准备目标类,声明变量并创建 setter 方法,方便后续注入
    private CustomerDao customerDao;
    private MyAspect myAspect;

    public CustomerDao getCustomerDao() {
        return customerDao;
    }

    public void setCustomerDao(CustomerDao customerDao) {
        this.customerDao = customerDao;
    }

    public MyAspect getMyAspect() {
        return myAspect;
    }

    public void setMyAspect(MyAspect myAspect) {
        this.myAspect = myAspect;
    }

    /**
     * 其中 Proxy 的 newProxyInstance() 方法的第一个参数是当前类的类加载器,第二参数是所创建实例的实现类的接口,第三个参数就是需要增强的方法。
     * 在目标类方法执行的前后,分别执行切面类中的 myBefore() 方法和 myAfter() 方法。
     */
    public CustomerDao getBean() {
        // 
        return  (CustomerDao) Proxy.newProxyInstance(
                MyBeanFactory.class.getClassLoader(), new Class[]{CustomerDao.class}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        myAspect.myBefore();    // 前增强
                        Object obj = method.invoke(customerDao, args);
                        myAspect.myAfter();     // 后增强
                        return obj;
                    }
                }
        );
    }
}

其中 getBean 方法体中的代码就是使用代理类对创建的实例 customerDao 中的方法进行增强的代码,其中 Proxy 的 newProxyInstance() 方法的第一个参数是当前类的类加载器,第二参数是所创建实例的实现类的接口,第三个参数就是需要增强的方法。

在目标类方法执行的前后,分别执行切面类中的 myBefore() 方法和 myAfter() 方法。

7)修改 applicationContext 配置文件

这里采用设值注入和自动装配的方式

<?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对象  -->
    <bean id = "customerDao" class="com.mengma.dao.CustomerDaoImpl"></bean>
    <bean id = "myAspect" class="com.mengma.jdk.MyAspect"></bean>
    <bean id = "myBeanFactory" class="com.mengma.jdk.MyBeanFactory" autowire="byName"></bean>
</beans>

8)创建测试类 JDKProxyTest

package com.mengma.jdk;

import com.mengma.dao.CustomerDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class JDKProxyTest {
    public static void main(String[] args) {
        String xmlPath = "applicationContext.xml";
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
        MyBeanFactory myBeanFactory = (MyBeanFactory) applicationContext.getBean("myBeanFactory");
        CustomerDao customerDao = myBeanFactory.getBean();
        // 从 Spring 容器中获得指定的代理对象
        // 执行方法
        customerDao.add();
        customerDao.update();
        customerDao.delete();
        customerDao.find();
    }
}

9)执行结果

image-20210105160727808

ⅩⅦ、Spring CGLlB动态代理(附带实例)

通过《Spring JDK动态代理》教程的学习可以知道,JDK 动态代理使用起来非常简单,但是它也有一定的局限性,这是因为 JDK 动态代理必须要实现一个或多个接口,如果不希望实现接口,则可以使用 CGLIB 代理。

CGLIB(Code Generation Library)是一个高性能开源的代码生成包,它被许多 AOP 框架所使用,其底层是通过使用一个小而快的字节码处理框架 ASM(Java 字节码操控框架)转换字节码并生成新的类。因此 CGLIB 要依赖于 ASM 的包,解压 Spring 的核心包 spring-core-3.2.2.RELEASE.jar,文件目录如图所示。

spring-core-3.2.2.RELEASE.jar文件

在图中可以看出,解压的核心包中包含 cglib 和 asm,也就是说 Spring3.2.13 版本的核心包已经集成了 CGLIB 所需要的包,所以在开发中不需要另外导入 ASM 的 JAR 包了。下面通过案例演示实现 CGLIB 的代理过程。

案例

在 JDK 动态代理的代码基础上,进行添加和修改

1)在 dao 模块下新建一个类 GoodsDao

package com.mengma.dao;

public class GoodsDao {
    public void add() {
        System.out.println("添加商品...");
    }
    public void update() {
        System.out.println("修改商品...");
    }
    public void delete() {
        System.out.println("删除商品...");
    }
    public void find() {
        System.out.println("修改商品...");
    }
}

2)新建一个项目模块 com.mengma.cglib ,在模块下新建一个 MyBeanFactory 类

package com.mengma.cglib;

import com.mengma.dao.GoodsDao;
import com.mengma.jdk.MyAspect;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;


public class MyBeanFactory {
    private GoodsDao goodsDao;
    private MyAspect myAspect;

    public void setGoodsDao(GoodsDao goodsDao) {
        this.goodsDao = goodsDao;
    }

    public void setMyAspect(MyAspect myAspect) {
        this.myAspect = myAspect;
    }

    // 创建代理类的返回方法
    public GoodsDao getCglibProxy(){
        // 生成代理类,CGLIB 在运行时,生成指定对象的子类
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(goodsDao.getClass());	// 指定代理对象
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                myAspect.myBefore();
                Object result = method.invoke(goodsDao,objects);
                myAspect.myAfter();
                return result;
            }
        });
        // 创建代理类
        GoodsDao goodsDaoProxy = (GoodsDao) enhancer.create();

        return goodsDaoProxy;
    }
    // 确定需要增强的类


}

3)创建测试类 CglibProxyTest

package com.mengma.cglib;

import com.mengma.dao.GoodsDao;
import com.mengma.jdk.MyAspect;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class CglibProxyTest {
    public static void main(String[] args) {
        String xmlPath = "applicationContext.xml";
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
        MyBeanFactory myBeanFactory = (MyBeanFactory) applicationContext.getBean("myBeanFactory1");
        GoodsDao goodsDaoProxy = (GoodsDao) myBeanFactory.getCglibProxy();
        goodsDaoProxy.add();
        goodsDaoProxy.delete();
        goodsDaoProxy.find();
        goodsDaoProxy.update();
    }
}

对于配置文件的更改,留作思考

4)运行检查

image-20210106154255321

ⅩⅧ、Spring通知类型及使用ProxyFactoryBean创建AOP代理

在《Spring JDK动态代理》和《Spring CGLlB动态代理》中,讲解了 AOP 手动代理的两种方式,下面通过讲解 Spring 的通知介绍 Spring 是如何创建 AOP 代理的。

Spring 通知类型

通过前面的学习可以知道,通知(Advice)其实就是对目标切入点进行增强的内容,Spring AOP 为通知(Advice)提供了 org.aopalliance.aop.Advice 接口。

Spring 通知按照在目标类方法的连接点位置,可以分为以下五种类型,如表 1 所示。

名称 说明
org.springframework.aop.MethodBeforeAdvice(前置通知) 在方法之前自动执行的通知称为前置通知,可以应用于权限管理等功能。
org.springframework.aop.AfterReturningAdvice(后置通知) 在方法之后自动执行的通知称为后置通知,可以应用于关闭流、上传文件、删除临时文件等功能。
org.aopalliance.intercept.MethodInterceptor(环绕通知) 在方法前后自动执行的通知称为环绕通知,可以应用于日志、事务管理等功能。
org.springframework.aop.ThrowsAdvice(异常通知) 在方法抛出异常时自动执行的通知称为异常通知,可以应用于处理异常记录日志等功能。
org.springframework.aop.IntroductionInterceptor(引介通知) 在目标类中添加一些新的方法和属性,可以应用于修改旧版本程序(增强类)。

声明式 Spring AOP

Spring 创建一个 AOP 代理的基本方法是使用org.springframework.aop.framework.ProxyFactoryBean,这个类对应的切入点和通知提供了完整的控制能力,并可以生成指定的内容。

ProxyFactoryBean 类中的常用可配置属性如表 2 所示。

属性名称 描 述
target 代理的目标对象
proxyInterfaces 代理要实现的接口,如果有多个接口,则可以使用以下格式赋值: ...
proxyTargetClass 是否对类代理而不是接口,设置为 true 时,使用 CGLIB 代理
interceptorNames 需要植入目标的 Advice
singleton 返回的代理是否为单例,默认为 true(返回单实例)
optimize 当设置为 true 时,强制使用 CGLIB

实例

1)导入依赖

2)创建切面类 MyAspect

package com.mengma.factorybean;

// 使用编译器的自动导入的时候注意,Method* 两个类同时也存在于其他类下面,认真选择
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

// 需要实现接口,确定哪个通知,以及告诉 Spring 应该执行哪个方法
public class MyAspect implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("方法执行之前....");
        // 执行目标方法
        Object object = methodInvocation.proceed();
        System.out.println("方法执行之后....");
        return object;
    }
}

3)在 com.mengma.factorybean 下创建配置文件 applicationContext.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.xsd">
    <!--  目标类  -->
    <bean id="customerDao" class="com.mengma.dao.CustomerDaoImpl"></bean>
    <!--  通知 advice  -->
    <bean id="myAspect" class="com.mengma.factorybean.MyAspect"></bean>
    <!--    生成代理对象       -->
    <bean id="customerDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--  代理接口的实现  -->
        <property name="proxyInterfaces" value="com.mengma.dao.CustomerDao"/>
        <!--   代理的目标对象     -->
        <property name="target" ref="customerDao"/>
        <!--    用通知增强目标    -->
        <property name="interceptorNames" value="myAspect"/>
        <!--    如何生成代理,true:使用cglib ;false:使用 jdk 动态代理-->
        <property name="proxyTargetClass" value="true"/>
    </bean>
</beans>

5)创建测试类 MyFactoryBeanTest

package com.mengma.factorybean;

import com.mengma.dao.CustomerDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyBeanFactoryTest {
    public static void main(String[] args) {
        String xmlPath = "com\\mengma\\factorybean\\applicationContext.xml";
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
        CustomerDao customerDao = (CustomerDao) applicationContext.getBean("customerDaoProxy");
        customerDao.find();
        customerDao.add();
        customerDao.delete();
        customerDao.update();
    }
}

6)运行测试

image-20210106162100778

ⅩⅨ、Spring使用AspectJ开发AOP:基于XML和基于Annotation

AspectJ 是一个基于 Java 语言的 AOP 框架,它扩展了 Java 语言。Spring 2.0 以后,新增了对 AspectJ 方式的支持,新版本的 Spring 框架,建议使用 AspectJ 方式开发 AOP。

使用 AspectJ 开发 AOP 通常有两种方式:

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

实际上项目开发中使用的一般是 Annotation 的声明式,这也是官方推荐的,接下来将对基于 Annotation AOP 的开发方式进行讲解。(如有对基于 XML 声明式的需要可以自行搜索)

1、基于 Annotation 的声明式

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

下面通过案例演示 Spring 中如何使用基于 XML 的声明式实现 AOP 的开发。

1)添加依赖

使用 AspectJ 除了要导入 Spring AOP 的 JAR 包(默认已经导入)之外,还需要导入 AspectJ 相关的 JAR 包,可以在 官方仓库 下载自己需要的版本。下载完毕之后将 JAR 包解压,在压缩包 file -> lib 下面

image-20210107105055869

解压出来后把 JAR 包添加到项目结构的全局库中:
image-20210106173654668

image-20210106173805902

点击 + 号,选择 Java 后选择刚才解压好的 AspectJ JAR 包,将其加入到依赖中,确定

2)创建切面类 MyAspect

package com.mengma.aspectj.xml;


import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component      // @Aspect 注解一定要和 @Component 一起使用,不然拦截不到方法
public class MyAspect {
    @Pointcut("execution(* com.mengma.dao..*(..))")
    private void myPointCut(){
    }
    // 前置通知
    @Before("myPointCut()")
    public void myBefore (JoinPoint joinPoint){
        System.out.print("前置通知,目标:");
        System.out.print(joinPoint.getTarget() + "方法名称:");
        System.out.println(joinPoint.getSignature().getName()); // 调用方法获取方法名称
    }

    // 后置通知
    @AfterReturning("myPointCut()")
    public void myAfterReturning(JoinPoint joinPoint){
        System.out.print("后置通知,方法名称:" + joinPoint.getSignature().getName());
    }

    // 环绕通知
    @Around("myPointCut()")
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕开始");
        Object obj = proceedingJoinPoint.proceed();
        System.out.println("环绕结束");
        return obj;
    }

    // 异常通知
    @AfterThrowing(value = "myPointCut()" ,throwing = "e")
    public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
        System.out.println("异常通知" + "出错了" + e.getMessage());
    }

    // 最终通知
    @After("myPointCut()")
    public void myAfter(){
        System.out.println("最终通知");
    }
}

2)修改配置文件

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    
    <!--  扫描包下的注解  -->
    <context:component-scan base-package="com.mengma"></context:component-scan>
    <!--  启用 AspectJ 的自动代理  -->
    <aop:aspectj-autoproxy/>
</beans>

3)创建测试类

package com.mengma.aspectj.xml;

import com.mengma.dao.CustomerDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;

import javax.swing.*;

public class Test {
    public static void main(String[] args) {
        String xmlPath = "com\\mengma\\aspectj\\xml\\applicationContext.xml";
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
        CustomerDao customerDao = (CustomerDao) applicationContext.getBean("customerDao");

        customerDao.add();
    }

}

4)运行测试

image-20210107114214048

ⅩⅩ、Spring JDBCTemplate简介

Spring 框架针对数据库开发中的应用提供了 JDBCTemplate 类,该类是 Spring 对 JDBC 支持的核心,它提供了所有对数据库操作功能的支持。

Spring 框架提供的JDBC支持主要由四个包组成,分别是 core(核心包)、object(对象包)、dataSource(数据源包)和 support(支持包),org.springframework.jdbc.core.JdbcTemplate 类就包含在核心包中。作为 Spring JDBC 的核心,JdbcTemplate 类中包含了所有数据库操作的基本方法。

JdbcTemplate 类继承自抽象类 JdbcAccessor,同时实现了 JdbcOperations 接口。其直接父类 JdbcAccessor 为子类提供了一些访问数据库时使用的公共属性,具体介绍如下。

1)DataSource

其主要功能是获取数据库连接,具体实现时还可以引入对数据库连接的缓冲池和分布式事务的支持,它可以作为访问数据库资源的标准接口。

2)SQLExceptionTranslator

org.springframework.jdbc.support.SQLExceptionTranslator 接口负责对 SQLException 进行转译工作。通过必要的设置或者获取 SQLExceptionTranslator 中的方法,可以使 JdbcTemplate 在需要处理 SQLException 时,委托 SQLExceptionTranslator 的实现类完成相关的转译工作。

JdbcOperations 接口定义了在 JdbcTemplate 类中可以使用的操作集合,包括添加、修改、查询和删除等操作。

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

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http:/www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.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/spring" />
        <!--连接数据库的用户名-->
        <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>

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

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

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

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

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

在 JdbcTemplate 类中,提供了大量的查询和更新数据库的方法,如 query()、update() 等。关于这些方法的具体使用,将在下一节的案例中讲解,此处读者了解即可。

ⅩⅩⅠ、使用 JUnit 单元测试进行代码逻辑性校验

测试是检查应用程序是否是工作按照要求,并确保在开发者水平,单元测试进入功能性的处理。单元测试是单一实体(类或方法)的测试。 单元测试在每一个软件公司开发高品质的产品给他们的客户是十分必要的。

单元测试可以通过两种方式来完成:

手动测试

  • 费时和乏味:由于测试案例是由人力的,所以它是非常缓慢而乏味的执行。
  • 巨大的人力资源的投入:作为测试用例需要手动执行,所以更多的测试都需要手动测试。
  • 较不可靠:手动测试是为测试可能不会被精确地每次执行,因为人为错误导致不可靠。
  • 非可编程:无需编程就可以做,获取信息隐藏复杂的测试。

自动测试:

以工具支持,并通过使用自动化工具则称为自动化测试执行测试用例。

  • 快速自动化运行测试用例比人力显著更快。
  • 人力资源的投入较少:测试用例是通过使用自动化工具,所以较少测试者都需要在自动化测试执行。
  • 更可靠:自动化测试在每次运行的时间进行精确的相同操作。
  • 可编程:测试人员可以编写复杂的测试,以带出隐藏的信息。

1、添加 JUnit 的依赖

工欲善其事,必先利其器。作用后面再说,先说怎么安装。这里将如何在 IDEA 中添加依赖,如果使用的是 Eclipse,则参见 Eclipse JUnit 依赖的添加

我使用的是 IDEA ,IDEA 要使用 JUnit 单元测试功能需要专业版

在设置中的插件模块中将 JUnit 所需插件开启,JUnitGenerator 需要重新下载。

image-20210109151905430

开启过后无需去官网下载相应的 JAR 包,笔者使用的 IDEA 2020 版本已经自带了 JUnit 4 & 5 版本,但是需要主动的将其添加到项目路径。

随意新建一个类,在其中键入 @Test 注解,在未导入项目路径时,IDEA 自身的错误检查机制会报错,这时只需要借用 IDEA 的错误自动处理机制就可以将依赖添加进项目路径。如图:

image-20210109152552700

点击添加你需要的版本即可,至此项目依赖添加完毕。

然后指定一个目录为 test 目录,新建或者选取已存在的目录将其属性更改为测试源 根(如果不是中文的话,反正绿色的)

image-20210109153141444

2、什么是 JUnit 以及它的作用

JUnit是一个Java编程语言编写的单元测试框架。 重要的是在测试驱动开发中,并且是一个家族的统称为xUnit单元测试框架中的一个。

JUnit促进“先测试再编码”,它强调建立测试数据的一段代码可以被测试,先测试再编码实现的想法。这种做法就像是“试了一下,码了一点,测试了一下,代码一点点......”这增加了程序员的工作效率和程序代码的稳定性,减少程序员的压力和花在调试的时间。

2.1、JUnit 作用

  1. 可以书写一系列的测试方法,对项目所有的接口或者方法进行单元测试。
  2. 启动后,自动化测试,并判断执行结果, 不需要人为的干预。
  3. 只需要查看最后结果,就知道整个项目的方法接口是否通畅。
  4. 每个单元测试用例相对独立,由Junit 启动,自动调用。不需要添加额外的调用语句。
  5. 添加,删除,屏蔽测试方法,不影响其他的测试方法。 开源框架都对JUnit 有相应的支持。

实例:

1、创建测试用数据接口 JUnitDao

package JUnit单元测试反射等JDK8新特性.JUnit.dao;

public interface JUnitDao {
    int sum(int a,int b );
    int sub(int a,int b );
}

2、创建实现类 JUnitDaoImpl

package JUnit单元测试反射等JDK8新特性.JUnit.dao;

import org.springframework.stereotype.Component;

@Component("junitDao")
public class JUnitDaoImpl implements JUnitDao{
    @Override
    public int sum(int a, int b) {
        return a + b;
    }

    @Override
    public int sub(int a, int b) {
        return a - b;
    }
}

3、在实现类界面单击右键

image-20210109154709154

image-20210109154725113

image-20210109154738317

按步骤进入如上界面,选中想要测试的方法,之所以选择这个而不是让插件直接生成全部的原因是在实际开发中一个类中方法可能并不需要每一个都进行测试。生成之后如下:

package JUnit单元测试反射等JDK8新特性.JUnit.dao;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

class JUnitDaoImplTest {

    @BeforeEach
    void setUp() {
    }

    @AfterEach
    void tearDown() {
    }

    @Test
    void sum() {
    }

    @Test
    void sub() {
    }
}

4、在相应的测试方法中添加方法体进行测试,其中的注解(JUnit 4)如下:

Junit常用注解(Junit4.xxxx版本)
    * @Before:用来修饰方法,该方法会在每一个测试方法执行之前执行一次。
    * @After:用来修饰方法,该方法会在每一个测试方法执行之后执行一次。
    * @BeforeClass:用来静态修饰方法,该方法会在所有测试方法之前执行一次。
    * @AfterClass:用来静态修饰方法,该方法会在所有测试方法之后执行一次。

Junit常用注解(Junit5.xxxx版本)
     * @BeforeEach:用来修饰方法,该方法会在每一个测试方法执行之前执行一次。
     * @AfterEach:用来修饰方法,该方法会在每一个测试方法执行之后执行一次。
     * @BeforeAll:用来静态修饰方法,该方法会在所有测试方法之前执行一次。
     * @AfterAll:用来静态修饰方法,该方法会在所有测试方法之后执行一次。

PS:貌似 AspectJ 不能拦截到在 JUnit 中调用的类的方法,未做检验

ⅩⅩⅡ、Spring事务管理接口:PlatformTransactionManager、TransactionDefinition和TransactionStatus

Spring 的事务管理是基于 AOP 实现的,而 AOP 是以方法为单位的。Spring 的事务属性分别为传播行为、隔离级别、只读和超时属性,这些属性提供了事务应用的方法和描述策略。

Java EE 开发经常采用的分层模式中,Spring 的事务处理位于业务逻辑层,它提供了针对事务的解决方案。

在 Spring 解压包的 libs 目录中,包含一个名称为 spring-tx-3.2.13.RELEASE.jar 的文件,该文件是 Spring 提供的用于事务管理的 JAR 包,其中包括事务管理的三个核心接口:PlatformTransactionManager、TransactionDefinition 和 TransactionStatus。

将该 JAR 包的后缀名 jar 改成 zip 的形式后,解压压缩包,进入解压文件夹中的 \org\springframework\transaction 目录后,该目录中的文件如图所示。

事务管理核心接口

在图中,方框所标注的三个文件就是本节将要讲解的核心接口。这三个核心接口的作用及其提供的方法如下。

1. PlatformTransactionManager

PlatformTransactionManager 接口是 Spring 提供的平台事务管理器,用于管理事务。该接口中提供了三个事务操作方法,具体如下。

  • TransactionStatus getTransaction(TransactionDefinition definition):用于获取事务状态信息。
  • void commit(TransactionStatus status):用于提交事务。
  • void rollback(TransactionStatus status):用于回滚事务。

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

2. TransactionDefinition

TransactionDefinition 接口是事务定义(描述)的对象,它提供了事务相关信息获取的方法,其中包括五个操作,具体如下。

  • String getName():获取事务对象名称。
  • int getIsolationLevel():获取事务的隔离级别。
  • int getPropagationBehavior():获取事务的传播行为。
  • int getTimeout():获取事务的超时时间。
  • boolean isReadOnly():获取事务是否只读。

在上述五个方法的描述中,事务的传播行为是指在同一个方法中,不同操作前后所使用的事务。传播行为的种类如表 1 所示。

属性名称 描 述
PROPAGATION_REQUIRED required 支持当前事务。如果 A 方法已经在事务中,则 B 事务将直接使用。否则将创建新事务
PROPAGATION_SUPPORTS supports 支持当前事务。如果 A 方法已经在事务中,则 B 事务将直接使用。否则将以非事务状态执行
PROPAGATION_MANDATORY mandatory 支持当前事务。如果 A 方法没有事务,则抛出异常
PROPAGATION_REQUIRES_NEW requires_new 将创建新的事务,如果 A 方法已经在事务中,则将 A 事务挂起
PROPAGATION_NOT_SUPPORTED not_supported 不支持当前事务,总是以非事务状态执行。如果 A 方法已经在事务中,则将其挂起
PROPAGATION_NEVER never 不支持当前事务,如果 A 方法在事务中,则抛出异常
PROPAGATION.NESTED nested 嵌套事务,底层将使用 Savepoint 形成嵌套事务

在事务管理过程中,传播行为可以控制是否需要创建事务以及如何创建事务。

通常情况下,数据的查询不会改变原数据,所以不需要进行事务管理,而对于数据的增加、修改和删除等操作,必须进行事务管理。如果没有指定事务的传播行为,则 Spring3 默认的传播行为是 required。

3. TransactionStatus

TransactionStatus 接口是事务的状态,它描述了某一时间点上事务的状态信息。其中包含六个操作,具体如表 2 所示。

名称 说明
void flush() 刷新事务
boolean hasSavepoint() 获取是否存在保存点
boolean isCompleted() 获取事务是否完成
boolean isNewTransaction() 获取是否是新事务
boolean isRollbackOnly() 获取是否回滚
void setRollbackOnly() 设置事务回滚
posted @ 2021-01-25 20:41  绯狱丸丶  阅读(154)  评论(0编辑  收藏  举报