当前位置 : 首页 > 维修数据

Spring框架:IoC与AOP核心机制解析

<|begin▁of▁sentence|># 1. 概述 ## 1.1. 什么是Spring Spring是一个开源框架,它由[Rod Johnson](https://baike.baidu.com/item/Rod Johnson)创建。它是为了解决企业应用开发的复杂性而创建的。 Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。 然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。 **目的**:解决企业应用开发的复杂性 **功能**:使用基本的JavaBean代替EJB,并提供了更多的企业应用功能 **范围**:任何Java应用 简单来说,Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。 ## 1.2. Spring框架的特点 - **轻量**:从大小与开销两方面而言Spring都是轻量的。完整的Spring框架可以在一个大小只有1MB多的JAR文件里发布。并且Spring所需的处理开销也是微不足道的。此外,Spring是非侵入式的:典型地,Spring应用中的对象不依赖于Spring的特定类。 - **控制反转**:Spring通过一种称作控制反转(IoC)的技术促进了松耦合。当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。你可以认为IoC与JNDI相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。 - **面向切面**:Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。 - **容器**:Spring包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器,你可以配置你的每个bean如何被创建——基于一个可配置原型(prototype),你的bean可以创建一个单独的实例或者每次需要时都生成一个新的实例——以及它们是如何相互关联的。然而,Spring不应该被混同于传统的重量级的EJB容器,它们经常是庞大与笨重的,难以使用。 - **框架**:Spring可以将简单的组件配置、组合成为复杂的应用。在Spring中,应用对象被声明式地组合,典型地是在一个XML文件里。Spring也提供了很多基础功能(事务管理、持久化框架集成等等),将应用逻辑的开发留给了你。 所有Spring的这些特征使你能够编写更干净、更可管理、并且更易于测试的代码。它们也为Spring中的各种模块提供了基础支持。 ## 1.3. Spring框架的组成 Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式,如图 1 所示。 ![img](https://img-blog.csdn.net/2018080715325843?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2OTEwNjM1/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下: - **核心容器**:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用*控制反转*(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。 - **Spring 上下文**:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。 - **Spring AOP**:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。 - **Spring DAO**:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。 - **Spring ORM**:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。 - **Spring Web 模块**:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。 - **Spring MVC 框架**:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。 # 2. IoC基础 ## 2.1. 什么是IoC IoC—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,IoC意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。如何理解好Ioc呢?理解好Ioc的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下: - **谁控制谁,控制什么**:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。 - **为何是反转,哪些方面反转了**:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。 用图例说明一下,传统程序设计如图2-1,都是主动去创建相关对象然后再组合起来: ![img](https://img-blog.csdn.net/20180807153308439?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2OTEwNjM1/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 图2-1 传统应用程序示意图 当有了IoC/DI的容器后,在客户端类中不再主动去创建这些对象了,如图2-2所示: ![img](https://img-blog.csdn.net/20180807153317374?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2OTEwNjM1/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 图2-2有IoC/DI容器后程序结构示意图 ## 2.2. IoC能做什么 IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。 其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。 IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。 ## 2.3. IoC和DI DI—Dependency Injection,即“依赖注入”:**是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中**。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。 理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下: - **谁依赖于谁**:当然是应用程序依赖于IoC容器; - **为什么需要依赖**:应用程序需要IoC容器来提供对象需要的外部资源; - **谁注入谁**:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象; - **注入了什么**:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。 IoC和DI由什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。 看过很多对Spring的Ioc理解的文章,好多人对Ioc和DI的解释都晦涩难懂,反正就是一种说不清,道不明的感觉,读完之后依然是一头雾水,感觉就是开涛这位技术牛人写得特别通俗易懂,他清楚地解释了IoC(控制反转) 和DI(依赖注入)中的每一个字,读完之后给人一种豁然开朗的感觉。我相信对于初学Spring框架的人对Ioc的理解应该是有很大帮助的。 ## 2.4. Spring IoC容器 在Spring中,**Spring IoC容器是实现IoC的载体**,它可以在对象生成或初始化时直接将数据注入到对象中,也可以通过将对象引用注入到对象数据域中的方式来注入对方法调用的依赖。这种依赖注入是可以递归的,对象被逐层注入。就此而言,这种方案是完全非侵入式的,完全不需要依赖容器API,对象没有意识到容器的存在,容器对对象来说是完全无感知的。这样,对象可以在容器内外工作,这就赋予了对象极大的灵活性,使得它们可以脱离容器进行单元测试,唯一要做的只是创建一个模拟依赖对象的实现。 Spring IoC容器的设计主要是基于BeanFactory和ApplicationContext两个接口,其中ApplicationContext是BeanFactory的子接口之一,换句话说BeanFactory是Spring IoC容器所定义的最底层接口,而ApplicationContext是其高级接口之一,并且对BeanFactory功能做了许多有用的扩展,所以在绝大部分的工作场景下,都会使用ApplicationContext作为Spring IoC容器。 # 3. Bean配置 ## 3.1. 什么是Bean 对于Spring而言,一切Java类都是资源,而资源都是Bean,容纳这些Bean的是Spring所提供的IoC容器,所以Spring是一种基于Bean的编程。Bean就是由Spring容器初始化、装配及管理的对象,除此之外,Bean就与应用程序中的其他对象没有什么区别了。而Bean定义以及Bean相互间的依赖关系将通过配置元数据来描述。 ## 3.2. Bean的配置方式 在Spring中,Bean的配置方式有两种,一种是基于XML的配置方式,一种是基于注解的配置方式。在实际开发中,我们通常采用基于注解的配置方式,因为这种方式更加简洁、直观,而且可以减少XML配置文件的编写。但是,基于XML的配置方式也是非常重要的,因为它是Spring框架的基础,而且有些配置只能通过XML来完成。 ### 3.2.1. 基于XML的配置方式 在基于XML的配置方式中,我们需要在XML配置文件中定义Bean,并指定Bean的类、属性、依赖关系等信息。Spring IoC容器会根据XML配置文件来创建Bean实例,并完成依赖注入。 下面是一个简单的XML配置文件示例: ```xml ``` 在上面的配置文件中,我们定义了两个Bean:userService和userDao。其中,userService依赖于userDao,我们通过``标签来注入依赖。 ### 3.2.2. 基于注解的配置方式 在基于注解的配置方式中,我们不需要编写XML配置文件,而是通过在Java类上添加注解来完成Bean的定义和依赖注入。Spring提供了一系列的注解,如`@Component`、`@Service`、`@Repository`、`@Controller`等,用于标识Bean的角色。同时,还提供了`@Autowired`、`@Resource`等注解,用于完成依赖注入。 下面是一个基于注解的配置示例: ```java @Service public class UserService { @Autowired private UserDao userDao; // ... } @Repository public class UserDaoImpl implements UserDao { // ... } ``` 在上面的代码中,我们使用`@Service`注解标识UserService是一个服务层的Bean,使用`@Repository`注解标识UserDaoImpl是一个数据访问层的Bean。同时,使用`@Autowired`注解自动注入userDao依赖。 ## 3.3. Bean的作用域 在Spring中,Bean的作用域定义了Bean的生命周期和可见范围。Spring提供了多种作用域,包括: - **singleton**:默认作用域,每个Spring IoC容器中只有一个Bean实例。 - **prototype**:每次请求都会创建一个新的Bean实例。 - **request**:每次HTTP请求都会创建一个新的Bean实例,仅适用于Web应用。 - **session**:每个HTTP会话都会创建一个新的Bean实例,仅适用于Web应用。 - **global session**:每个全局HTTP会话都会创建一个新的Bean实例,仅适用于Portlet应用。 我们可以通过`@Scope`注解或XML配置来指定Bean的作用域。 ## 3.4. Bean的生命周期 Spring IoC容器负责管理Bean的生命周期,包括Bean的创建、初始化、使用和销毁。我们可以通过实现`InitializingBean`和`DisposableBean`接口,或者在Bean定义中指定`init-method`和`destroy-method`方法来定制Bean的初始化和销毁行为。 # 4. AOP基础 ## 4.1. 什么是AOP AOP(Aspect-Oriented Programming,面向切面编程)是Spring框架的另一个核心特性。它允许开发者将横切关注点(如日志、事务、安全等)从业务逻辑中分离出来,从而实现关注点的模块化。AOP通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。 ## 4.2. AOP的核心概念 - **切面(Aspect)**:一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是Java应用中一个很好的横切关注点例子。切面用Spring的Advisor或切面类实现。 - **连接点(Joinpoint)**:程序执行过程中的某个特定点,比如方法的调用或异常的抛出。在Spring AOP中,一个连接点总是代表一个方法的执行。 - **通知(Advice)**:在切面的某个特定的连接点上执行的动作。通知有各种类型,包括“around”、“before”和“after”等通知。许多AOP框架(包括Spring)都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。 - **切入点(Pointcut)**:匹配连接点的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时)。切入点表达式如何和连接点匹配是AOP的核心:Spring默认使用AspectJ切入点语法。 - **引入(Introduction)**:在不修改类代码的前提下,为类添加新的方法和属性。 - **目标对象(Target Object)**:被一个或多个切面所通知的对象。也被称做被通知(advised)对象。既然Spring AOP是通过运行时代理实现的,这个对象永远是一个被代理(proxied)对象。 - **AOP代理(AOP Proxy)**:AOP框架创建的对象,用来实现切面契约(例如通知方法执行等等)。在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。 - **织入(Weaving)**:把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。 ## 4.3. AOP的实现方式 Spring AOP支持两种方式的AOP实现: - **基于代理的经典Spring AOP**:使用ProxyFactoryBean创建AOP代理,需要实现相应的Advice接口。 - **@AspectJ注解风格的切面**:使用AspectJ注解来定义切面,Spring使用AspectJ提供的库进行解析和匹配。 在实际开发中,我们通常使用@AspectJ注解风格来定义切面,因为它更加简洁和直观。 ## 4.4. AOP的应用场景 AOP通常用于处理横切关注点,如: - **日志记录**:为方法调用添加日志记录。 - **事务管理**:为方法调用添加事务管理。 - **安全控制**:检查用户权限。 - **性能监控**:监控方法执行时间。 - **异常处理**:统一处理异常。 通过AOP,

栏目列表