0%

理解 Spring 的控制反转和依赖注入以及 AOP

依赖倒置原则

在了解控制反转之前,有必要了解软件设计里的一个重要思想:依赖倒置原则

这里可以举一个汽车的例子来说明。假如我们来设计一辆汽车,先设计轮子,再根据轮子的大小去设计底盘,根据底盘去设计车身,最终根据车身来设计好整个车子。这样就会形成了一个如下的依赖关系:

这种设计的维护性非常低。假如完工之后,需要更改轮子的设计,那么轮子上层的所有事物的设计都得修改,即整个设计都需要修改。

所以,我们需要换一种思路来实现。先设计出汽车的模型,再设计车身,根据车身设计底盘的大小,最后设计底层的轮子。那么,依赖关系就导致了。轮子的设计取决于底盘,底盘取决于车身,车身依赖车子的设计。所以,依赖关系变成了这样:

这样,当需要修改轮子时,只要改动轮子自己的设计就可,因为没有实物是依赖于轮子的。如果是修改底盘,也只会影响到它底层的轮子,高层的事物不会被影响,就不会出现「牵一发而动全身」的情况了。

所以,原本是「高层建筑依赖底层建筑」,而现在变成了「底层建筑依赖于高层建筑」。

控制反转和依赖注入

Spring 中经常提到的控制反转(Inversion of Control,IOC)和依赖注入(dependency injection-DI)是等同的概念,控制反转是通过通过依赖注入实现的。所谓的依赖注入指的是:

容器负责创建对象和维护对象间的依赖关系,而不是通过对象本身来负责自己的创建和解决自己的依赖。

即 Spring 这个容器替开发者管理着一系列的类,需要的时候,就不用自己去定义,而是直接向 Spring 容器去索取。

依赖注入的主要目的是为了解耦,这是一种组合的概念。举个例子,如果一个类要具有某个功能时,如果是继承具有此功能的父类,那么该子类就与父类耦合。而如果这个类是去组合具有这个功能的其它类,那么耦合度就会大大降低。

注入方式

  • set方式注入
  • 构造方法注入
  • 字段注入

注入类型

  • 值类型注入
  • 引用类型注入

Spring IoC 容器(ApplicationContext)是负责创建 Bean,并通过容器将功能类 Bean 注入到自己需要的 Bean 中。Spring 提供了多种不同的方式,如使用 XML、注解、Java配置等来实现 Bean 的创建和注入。以上的这些配置方式都被称为是配置元数据,即描述数据的数据。Spring 容器通过解析这些配置元数据就可以进行 Bean 的初始化、配置和管理依赖等。

声明 Bean 的注解

  • @Component组件:没有明确的角色。
  • @Service:在业务逻辑层(Service 层)中使用
  • @Repository:在数据访问层(dao 层)中使用
  • @Controller:在表现层(MVC -> SpringMVC)中使用

注入 Bean 的注解,一般情况下通用(下面三个注解都可以注解在set方法或者属性上,优点是代码少,层次清晰):

@Autowired:Spring 提供的注解
@Inject:JSR-330 提供的注解
@Resource:JSR-250 提供的注解

ApplicationContext 和 BeanFactory 的区别

ApplicationContext 接口

  1. 每次容器启动时,就会创建容器中配置的所有对象
  2. 提供了更多的功能

BeanFactory 接口

  1. 是 Spring 的原始接口,针对原始接口的实现类的功能较为单一
  2. BeanFactory 接口实现类的容器,特点是每次在获得对象时才会创建对象

AOP

AOP 即面向切面编程。Spring 的 AOP 是用来解耦,AOP 可以让一组类共享相同的行为

面向对象是通过继承类和接口的方式,会使代码的耦合度增加。AOP 就是弥补了 OOP 的不足之处。它能够让那些与业务无关,但是却被业务模块共同调用的逻辑或者责任(例如事务处理、日志管理、权限控制等)封装起来,降低模块间的耦合度,有利于未来的拓展性和可维护性。

例子

例如,银行系统里会有一个「取款」的流程和「查询余额」的流程。如果把它们两个的步骤都列举出来,会发现有一个相同的「验证流程」。那么,这个验证用户的代码是否可以提取出来,不放进主流程里呢?这里 AOP 就起到作用了。

在编写上述这个例子时,我们可以完全不考虑验证用户的步骤。我们可以在另外一个地方,写好验证用户的代码,然后通过,比如 Spring,告知这段代码会被添加到哪些地方,Spring 就会帮助我们 copy 过去。这样就降低了模块间的耦合度。

核心概念

  • 横切关注点:要对哪些方法进行拦截,拦截后怎么处理,这些关注点都是横切关注点
  • 切面(aspect):切面是指对横切关注点的抽象
  • 连接点(joinpoint):指被拦截到的点,即被拦截的方法
  • 切入点(pointcut):对连接点进行拦截的定义
  • 通知(advice):拦截到连接点之间要执行的代码
  • 目标对象:代理的目标对象
  • 织入:将切面应用到目标对象并导致代理对象创建的过程
  • 引入:引入可以在运行期为类动态地添加一些方法或者字段

常用注解

  • @Controller:表明这个类是 Spring 里的 Controller,将其声明为 Spring 的一个 Bean,Dispatcher Servlet 会自动扫描注解了解此注解的类,并且会把 Web 请求映射到注解@RequestMapping中。
  • @RequestMapping:该注解是用来映射Web请求、处理类和方法的,可注解在类,也可注解在方法上。
  • @ResponseBody:允许请求的参数放在请求体中
  • @PathVariable:通过 path 可以看出,是用来接收路径参数的。
  • @RestController:这其实是一个组合注解,组合了@Controller@ResponseBody,所以如果是编写一个与界面交互数据有关的类,那么其实可以直接使用此注解。否则,就需要加@Controller@ResponseBody的注解。