文章目录
这一部分涵盖了 Spring 框架绝对不可或缺的所有技术。其中最重要的是 Spring Framework 的控制反转 (Inversion of Control, IoC) 容器,紧随其后的是 Spring 的面向切面编程 (Aspect-Oriented Programming, AOP) 技术的全面覆盖。 Spring Framework 有自己的 AOP 框架,它在概念上易于理解。
控制反转(Inversion of Control, IoC)
Spring IoC 容器和 Bean 介绍
IoC 也称为依赖项注入(dependency injection, DI),它是一个过程,对象仅通过构造函数参数、工厂方法的参数,或在对象实例构造或从工厂方法返回后设置的属性。然后容器在创建bean时注入这些依赖项。这个过程基本上是相反的(因此得名,控制反转)。bean 本身通过使用类的直接构造或服务定位器模式等机制来控制其依赖项的实例化或位置。
org.springframework.beans
和 org.springframework.context
包是 Spring Framework 的 IoC 容器的基础。 BeanFactory
接口提供了一种能够管理任何类型对象的高级配置机制,提供了配置框架和基本功能,ApplicationContext
添加了更多企业特定的功能。
在 Spring 中,构成应用程序主干并由 Spring IoC 容器管理的对象称为 bean。 bean 是由 Spring IoC 容器实例化、组装和管理的对象。否则,bean 只是应用程序中众多对象之一。 Bean 以及它们之间的依赖关系反映在容器使用的配置元数据中。
容器概述
org.springframework.context.ApplicationContext
接口代表 Spring IoC 容器,负责实例化、配置和组装 bean。容器通过读取配置元数据来获取有关要实例化、配置和组装哪些对象的指令。配置元数据以 XML、Java 注释或 Java 代码表示。它可以让您表达组成应用程序的对象以及这些对象之间丰富的相互依赖关系。
Spring 提供了 ApplicationContext
接口的几个实现。在独立应用程序中,通常创建 ClassPathXmlApplicationContext
或 FileSystemXmlApplicationContext
的实例。
下图展示了 Spring 是如何工作的。您的应用程序类与配置元数据相结合,因此在 ApplicationContext 被创建和初始化之后,您就有了一个完全配置且可执行的系统或应用程序。
配置元数据(Configuration Metadata)
以下示例显示了基于 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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions go here -->
</beans>
-
id
属性是一个字符串,用于标识单个 bean 定义 -
class
属性定义了 bean 的类型并使用了完全合格的类名
实例化一个容器
提供给 ApplicationContext
构造函数的一个或多个位置路径资源字符串,它允许容器从各种外部资源(例如本地文件系统、Java CLASSPATH 等)加载配置元数据
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
<!--service.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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- services -->
<bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="itemDao" ref="itemDao"/>
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions for services go here -->
</beans>
使用容器
ApplicationContext
是高级工厂的接口,能够维护不同 bean 及其依赖项的注册表。通过使用方法 T getBean(String name, Class<T> requiredType)
,可以检索 bean 的实例。
// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// use configured instance
List<String> userList = service.getUsernameList();
Bean 概述
Spring IoC 容器管理一个或多个 bean。这些 bean 是使用您提供给容器的配置元数据创建的(例如,以 XML <bean/>
定义的形式)。
在容器本身内,这些 bean 定义表示为 BeanDefinition 对象,其中包含(除其他信息外)以下元数据:
-
一个包限定的类名:通常是被定义的 bean 的实际实现类
-
Bean 行为配置元素,它说明 Bean 在容器中的行为方式(范围、生命周期回调等)
-
对 bean 执行其工作所需的其他 bean 的引用。这些引用也称为协作者或依赖项
-
要在新创建的对象中设置的其他配置设置
命名 Bean
每个 bean 都有一个或多个标识符。这些标识符在承载 bean 的容器中必须是唯一的。一个 bean 通常只有一个标识符。在基于 XML 的配置元数据中,您可以使用 id
属性、name
属性或两者来指定 bean 标识符。 id
属性允许您指定一个 id
。bean id 唯一性仍由容器强制执行,但不再由 XML 解析器强制执行。
实例化 Bean
bean 定义本质上是创建一个或多个对象的方法。当被询问时,容器会查看命名 bean 的方法,并使用该 bean 定义封装的配置元数据来创建(或获取)实际对象。
依赖注入
依赖注入 (DI) 是一个过程,其中对象仅通过构造函数参数、工厂方法的参数或对象实例在构造或构造后设置的属性来定义它们的依赖项(即,与它们一起工作的其他对象),从工厂方法返回。然后容器在创建 bean 时注入这些依赖项。这个过程基本上是 bean 本身的逆过程(因此得名,控制反转),通过使用类的直接构造或服务定位器模式自行控制其依赖项的实例化或位置。
面向切面编程(Aspect Oriented Programming, AOP)
面向切面编程(AOP)通过提供另一种思考程序结构的方式来补充面向对象编程(OOP)。 OOP 中模块化的关键单位是类,而 AOP 中模块化的单位是切面。切面能够实现跨越多种类型和对象的关注点(例如事务管理)的模块化。(这种关注点在 AOP 文献中通常被称为“横切”关注点)
Spring 的关键组件之一是 AOP 框架。尽管 Spring IoC 容器不依赖于 AOP(这意味着如果您不想使用 AOP,则不需要使用 AOP),但 AOP 补充了 Spring IoC 以提供一个非常强大的中间件解决方案。
AOP 概念
- 切面:跨越多个类的关注点的模块化。事务管理是企业 Java 应用程序中横切关注点的一个很好的例子。在 Spring AOP 中,方面是通过使用常规类(基于模式的方法)或使用 @Aspect 注解注解的常规类来实现的。
- 编织:将切面与其他应用程序类型或对象联系起来以创建建议对象。这可以在编译时(例如,使用 AspectJ 编译器)、加载时或运行时完成。 Spring AOP 与其他纯 Java AOP 框架一样,在运行时执行编织。
Spring AOP 能力和目标
Spring AOP 的 AOP 方法不同于大多数其他 AOP 框架。目的不是提供最完整的 AOP 实现(尽管 Spring AOP 非常有能力)。相反,目的是提供 AOP 实现和 Spring IoC 之间的紧密集成,以帮助解决企业应用程序中的常见问题。
示例
参考 how2j 的教程,使用 Idea 完成
构建项目
新建一个普通 maven 项目,引入依赖
<!-- pom.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.wjl</groupId>
<artifactId>spring-how2j</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>spring-how2j-01-IOC</module>
<module>spring-how2j-02-Inject</module>
<module>spring-how2j-03-annotation-ioc</module>
<module>spring-how2j-04-aop</module>
<module>spring-how2j-05-annotation-aop</module>
</modules>
<properties>
<maven.compiler.source>16</maven.compiler.source>
<maven.compiler.target>16</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<version>2.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.9</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.9</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.9</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
<dependency>
<groupId>org.springframework.batch</groupId>
<artifactId>spring-batch-core</artifactId>
<version>4.3.3</version>
</dependency>
</dependencies>
</project>
右键项目,选择新建 Module ,新建一个普通 maven 项目,作为刚才创建项目的一个子项目,无需重复导入依赖。
IOC
新建子项目 spring-how2j-01-IOC
,创建 Category
、 Product
实体类
// Category.java
public class Category {
private int id;
private String name;
}
// Product.java
public class Product {
private int id;
private String name;
private Category category;
}
在 resources
目录下创建 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"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean name="c" class="com.wjl.pojo.Category">
<property name="name" value="category 1"/>
</bean>
<bean name="p" class="com.wjl.pojo.Product">
<property name="id" value="3"/>
<property name="name" value="Product 1"/>
<property name="category" ref="c"/>
</bean>
</beans>
新建一个测试类,使用 spring 容器创建实体类,并打印出类属性
public class TestSpring {
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Category c = (Category) context.getBean("c");
System.out.println(c.getName());
Product p = (Product) context.getBean("p");
System.out.println(p.toString());
}
}
AOP
对 Category
、 Product
实体类使用注解方法注入 spring 容器
@Component("c")
public class Category {
private int id;
private String name;
}
@Component("p")
public class Product {
private int id;
private String name;
private Category category;
}
为 Product
写一个简单的服务层
// ProductService.java
public interface ProductService {
void addProduct();
void deleteProduct();
void updateProduct();
void searchProduct();
}
// ProductServiceImpl.java
@Component("s")
public class ProductServiceImpl implements ProductService {
@Override
public void addProduct() { System.out.println("add a product"); }
@Override
public void deleteProduct() { System.out.println("delete a product"); }
@Override
public void updateProduct() { System.out.println("update products"); }
@Override
public void searchProduct() { System.out.println("search all products"); }
}
新建一个日志切面 LoggerAspect.java
,用来输出服务调用时间,统计服务运行时间
package com.wjl.aspect;
public class LoggerAspect {
public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date begin = new Date();
System.out.println("[" + simpleDateFormat.format(begin) + " start log] " + joinPoint.getSignature().getName());
long a = System.currentTimeMillis();
Object object = joinPoint.proceed();
long b = System.currentTimeMillis();
Date end = new Date();
System.out.println("[" + simpleDateFormat.format(end) + " end log] " + joinPoint.getSignature().getName());
System.out.println("it costs " + (double)(b - a) / 1000 + " s");
return object;
}
}
配置 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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="com.wjl.pojo"/>
<context:component-scan base-package="com.wjl.service"/>
<bean id="loggerAspect" class="com.wjl.aspect.LoggerAspect"/>
<aop:config>
<aop:pointcut id="loggerCutpoint" expression="execution(* com.wjl.service.ProductService.*(..))"/>
<aop:aspect id="logAspect" ref="loggerAspect">
<aop:around pointcut-ref="loggerCutpoint" method="log"/>
</aop:aspect>
</aop:config>
</beans>
创建测试类
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
ProductService s = (ProductService) context.getBean("s");
s.addProduct();
s.searchProduct();
}
全部运行结果
- Spring IOC/DI
- Spring 注入对象
- Spring 注解方式 IOC/DI
- Spring AOP
- Spring 注解方式 AOP
- Spring 注解方式测试
总结
Spring 的 IOC 和 AOP 大大提高了开发的效率,但是程序过分依赖配置文件,配置文件出错又不会像程序写错会有明显的提示,经常会因为配置文件写错导致程序运行不了,是名副其实的配置地狱。
Spring 凝聚了众多程序员的智慧, how2j 网站上的教程比较简单,没有触及更深层次的内容,还需要硬着头皮看官方文档,才能了解更多。