一、Spring 框架结构
1.1 简化Java开发
为了降低Java开发的复杂性,Spring采取了以下4种关键策略:
1. 基于POJO的轻量级和最小侵入性编程; 2. 通过依赖注入和面向接口实现松耦合; 3. 基于切面和惯例进行声明式编程; 4. 通过切面和模板减少样板式代码。1.1.1 激发POJO的潜能
在基于Spring构建的应用中,它的类通常没有任何痕迹表明你使用了Spring。最坏的场景是,一个类或许会使用Spring注解,但它依旧是POJO。
1.1.2 依赖注入
任何一个有实际意义的应用都会由两个或者更多的类组成,这些类相互之间进行协作来完成特定的业务
逻辑。按照传统的做法,每个对象负责管理与自己相互协作的对象(即它所依赖的对象)的引用,这将会导致高度耦合和难以测试的代码。通过DI,对象的依赖关系将由系统中负责协调各对象的第三方组件在创建对象的时候进行设定。对象无需自行创建或管理它们的依赖关系,依赖关系将被自动注入到需要它们的对象当中去。
松耦合: 如果一个对象只通过接口(而不是具体实现或初始化过程)来表明依赖关系,那么这种依赖就能够在对象本身毫不知情的情况下,用不同的具体实现进行替换。
装配( wiring):创建应用组件之间协作的行为。 Spring具有非常大的灵活性,它提供了三种主要的装配机制: 1. 在XML中进行显式配置 2. 在Java中进行显式配置 3. 隐式的bean发现机制和自动装配(基于注解)
建议是尽可能地使用自动配置的机制。显式配置越少越好。当你必须要显式配置bean的时候(比如,有些源码不是由你来维护的,而当你需要为这些代码配置bean的时候),我推荐使用类型安全并且比XML更加强大的JavaConfig。最后,只有当你想要使用便利的XML命名空间,并且在JavaConfig中没有同样的实现时,才应该使用XML。
Spring通过应用上下文( Application Context)装载bean的定义并把它们组装起来。 Spring应用上下文全权负责对象的创建和组装。 Spring自带了多种应用上下文的实现,它们之间主要的区别仅仅在于如何加载配置。 ClassPathXmlApplicationContext
1.1.3 应用切面
DI 能够让相互协作的软件组件保持松散耦合(使用接口而不是具体实现来表明依赖关系),而面向切面编程( aspect-oriented programming, AOP)允许你把遍布应用各处的功能分离出来形成可重用的组件。
AOP能够使这些服务(例如日志和安全等)模块化,并以声明的方式将它们应用到它们需要影响的组件中去。所造成的结果就是这些组件会具有更高的内聚性并且会更加关注自身的业务,完全不需要了解涉及系统服务所带来复杂性。总之, AOP能够确保POJO的简单性。1.1.4 使用模板消除样板式代码
样板式代码的一个常见范例是使用JDBC访问数据库查询数据。JMS、 JNDI和使用REST服务通常也涉及大
量的重复代码。 Spring旨在通过模板封装来消除样板式代码。 Spring的JdbcTemplate使得执行数据库操作时,避免传统的JDBC样板代码成为了可能。1.2 容纳你的Bean
在基于Spring的应用中,你的应用对象生存于Spring容器( container)中。Spring容器负责创建对象,装配它们,配置它们并管理它们的整个生命周期,从生存到死亡(在这里,可能就是new到finalize())。
容器是Spring框架的核心。 Spring容器使用DI管理构成应用的组件,它会创建相互协作的组件之间的关联。毫无疑问,这些对象更简单干净,更易于理解,更易于重用并且更易于进行单元测试。 Spring容器并不是只有一个。 Spring自带了多个容器实现,可以归为两种不同的类型。 bean工厂(由org.springframework. beans.factory.BeanFactory 接口定义)是最简单的容器,提供基本的DI支持。 应用上下文(由org.springframework.context.ApplicationContext接口定义)基于BeanFactory构建,并提供应用框架级别的服务,例如从属性文件解析文本信息以及发布应用事件给感兴趣的事件监听者。虽然我们可以在bean工厂和应用上下文之间任选一种,但bean工厂对大多数应用来说往往太低级了,因此,应用上下文要比bean工厂更受欢迎。
1.2.1 使用应用上下文
Spring自带了多种类型的应用上下文:
1. AnnotationConfigApplicationContext:从一个或多个基于Java的配置类中加载Spring应用上下文。
2. AnnotationConfigWebApplicationContext:从一个或多个基于Java的配置类中加载Spring Web应用上下文。 3. ClassPathXmlApplicationContext:从类路径下的一个或多个XML配置文件中加载上下文定义,把应用上下文的定义文件作为类资源。 4. FileSystemXmlapplicationcontext:从文件系统下的一个或多个XML配置文件中加载上下文定义。 5. XmlWebApplicationContext:从Web应用下的一个或多个XML配置文件中加载上下文定义。应用上下文准备就绪之后,我们就可以调用上下文的getBean()方法从Spring容器中获取bean。
1.2.2 bean的生命周期
在传统的Java应用中, bean的生命周期很简单。使用Java关键字new进行bean实例化,然后该bean就可以使用了。一旦该bean不再被使用,则由Java自动进行垃圾回收。
相比之下, Spring容器中的bean的生命周期就显得相对复杂多了。正确理解Spring bean的生命周期非常重要,因为你或许要利用Spring提供的扩展点来自定义bean的创建过程。图1.5展示了bean装载到Spring应用上下文中的一个典型的生命周期过程。图1.5 bean在Spring容器中从创建到销毁经历了若干阶段,每一阶段都可以针对Spring如何管理bean进行个性化定制;
1. Spring对bean进行实例化;
2. Spring将值和bean的引用注入到bean对应的属性中; 3.如果bean实现了BeanNameAware接口, Spring将bean的ID传递给setBean-Name()方法; 4.如果bean实现了BeanFactoryAware接口, Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入; 5.如果bean实现了ApplicationContextAware接口, Spring将调用setApplicationContext()方法,将bean所在的应用上下文的引用传入进来; 6.如果bean实现了BeanPostProcessor接口, Spring将调用它们的post-ProcessBeforeInitialization()方法; 7.如果bean实现了InitializingBean接口, Spring将调用它们的after-PropertiesSet()方法。类似地,如果bean使用 initmethod 声明了初始化方法,该方法也会被调用; 8.如果bean实现了BeanPostProcessor接口, Spring将调用它们的post-ProcessAfterInitialization()方法; 9.此时, bean已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁; 10.如果bean实现了DisposableBean接口, Spring将调用它的destroy()接口方法。同样,如果bean使用 destroy-method 声明了销毁方法,该方法也会被调用。
BeanPostProcessor 接口作用: 如果我们需要在Spring容器完成Bean的实例化、配置和其他的初始化前后添加一些自己的逻辑处理,我们就可以定义一个或者多个BeanPostProcessor接口的实现,然后注册到容器中。
Spring中Bean的实例化过程图示:
在 Spring 容器初始化 bean 和销毁前 所做的操作 的 定义方式有三种:
1. 通过@PostConstruct 和 @PreDestroy 方法 实现初始化和销毁bean之前进行的操作 2. 通过在xml 中定义 init-method 和 destroy-method 方法 3. 通过bean实现 InitializingBean 和 DisposableBean 接口
Bean 的作用域: 在默认情况下,Spring应用上下文中所有bean都是以单例( singleton)形式创建的。
Spring定义了多种作用域,可以基于这些作用域创建bean,包括:
单例( Singleton):在整个应用中,只创建bean的一个实例。 原型( Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。 会话( Session):在Web应用中,为每个会话创建一个bean实例。 请求( Rquest):在Web应用中,为每个请求创建一个bean实例。 单例是默认的作用域,但是正如之前所述,对于易变的类型,这并不合适。如果选择其他的作用域,要使用@Scope 注解,它可以与@Component或@Bean一起使用。JDK 基于接口的代理, 使用 CGLib 基于类的代理;
Spring提供了两种在运行时求值的方式: 1. 属性占位符( Property placeholder) 2. Spring表达式语言( SpEL)。
Spring切面可以应用5种类型的通知( Advice):
前置通知( Before):在目标方法被调用之前调用通知功能; 后置通知( After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么; 返回通知( After-returning):在目标方法成功执行之后调用通知; 异常通知( After-throwing):在目标方法抛出异常后调用通知; 环绕通知( Around):在被通知的方法调用之前和调用之后执行自定义的行为。织入( Weaving)是把切面应用到目标对象并创建新的代理对象的过程。在目标对象的生命周期里有多个点可以进行织入:
编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器。 AspectJ的织入编译器就是以这种方式织入切面的。类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器( ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。 AspectJ 5的加载时织入( load-time weaving, LTW)就支持以这种方式织入切面。运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时, AOP容器会为目标对象动态地创建一个代理对象。Spring AOP就是以这种方式织入切面的。 Spring提供了4种类型的AOP支持: 1. 基于代理的经典Spring AOP; 2. 纯POJO切面; 3. @AspectJ注解驱动的切面; 4. 注入式AspectJ切面(适用于Spring各版本)。 前三种都是Spring AOP实现的变体, Spring AOP构建在动态代理基础之上,因此 Spring对AOP的支持局限于方法拦截。 通过在代理类中包裹切面, Spring在运行期把切面织入到Spring管理的bean中。所以我们不需要特殊的编译器来织入Spring AOP的切面;
1.3 俯瞰Spring风景线
整个Spring Portfolio包括多个构建于核心Spring框架之上的框架和类库。概括地讲,整个Spring Portfolio几乎为每一个领域的Java开发都提供了Spring编程模型。
Spring Web Flow、Spring Web Service、Spring Security、Spring Integration、Spring Batch、Spring Data、Spring Social、Spring Mobile 、Spring for Android、Spring Boot;