依赖倒置原则
在了解控制反转之前,有必要了解软件设计里的一个重要思想:依赖倒置原则。
这里可以举一个汽车的例子来说明。假如我们来设计一辆汽车,先设计轮子,再根据轮子的大小去设计底盘,根据底盘去设计车身,最终根据车身来设计好整个车子。这样就会形成了一个如下的依赖关系:
这种设计的维护性非常低。假如完工之后,需要更改轮子的设计,那么轮子上层的所有事物的设计都得修改,即整个设计都需要修改。
所以,我们需要换一种思路来实现。先设计出汽车的模型,再设计车身,根据车身设计底盘的大小,最后设计底层的轮子。那么,依赖关系就导致了。轮子的设计取决于底盘,底盘取决于车身,车身依赖车子的设计。所以,依赖关系变成了这样:
这样,当需要修改轮子时,只要改动轮子自己的设计就可,因为没有实物是依赖于轮子的。如果是修改底盘,也只会影响到它底层的轮子,高层的事物不会被影响,就不会出现「牵一发而动全身」的情况了。
所以,原本是「高层建筑依赖底层建筑」,而现在变成了「底层建筑依赖于高层建筑」。
控制反转和依赖注入
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 接口
- 每次容器启动时,就会创建容器中配置的所有对象
- 提供了更多的功能
BeanFactory 接口
- 是 Spring 的原始接口,针对原始接口的实现类的功能较为单一
- 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
的注解。