为了降低Java开发的复杂性,Spring采取了以下4种关键策略:
- 基于POJO的轻量级和最小侵入性编程;
- 通过依赖注入和面向接口实现松耦合;
- 基于切面和惯例进行声明式编程;
- 通过切面和模板减少样板式代码。
Spring通过应用上下文(Application Context)装载bean的定义并把它们组装起来。Spring应用上下文全权负责对象的创建和组装。Spring 自带了多种应用上下文的实现,它们之间主要的区别仅仅在于如何加 载配置。
Spring容器应用上下文种类
- AnnotationConfigApplicationContext:从一个或多个 基于Java的配置类中加载Spring应用上下文。
- AnnotationConfigWebApplicationContext:从一个或 多个基于Java的配置类中加载Spring Web应用上下文。
- ClassPathXmlApplicationContext:从类路径下的一个或 多个XML配置文件中加载上下文定义,把应用上下文的定义文件 作为类资源。
- FileSystemXmlapplicationcontext:从文件系统下的一 个或多个XML配置文件中加载上下文定义。
- XmlWebApplicationContext:从Web应用下的一个或多个 XML配置文件中加载上下文定义。
Spring容器对象 bean 的生命周期
三种主要的装配机制:
- 在XML中进行显式配置。
- 在Java中进行显式配置。
- 隐式的bean发现机制和自动装配。
自动化装配
Spring从两个角度来实现自动化装配:
- 组件扫描(component scanning):Spring会自动发现应用上下文中所创建的bean。
- 自动装配(autowiring):Spring自动满足bean之间的依赖。
这里扫描涉及最基础的注解是:
@ComponentScan 和 @Component @ComponentScan 会扫描指定包下面,带有 @Component 注解的类,并将其创建对象,并且让Spring容器进行管理
自动装配注解是:@Autowired 根据类型进行自动装配
创建JavaConfig类的关键在于为其添加@Configuration注 解,@Configuration注解表明这个类是一个配置类,该类应该包 含在Spring应用上下文中如何创建bean的细节。
要在JavaConfig中声明bean,我们需要编写一个方法,这个方法会创 建所需类型的实例,然后给这个方法添加@Bean注解。默认情况下,bean的ID与带有@Bean注解的方法名是一样的。
通过xml装配
在XML配置中,这意味着要创建 一个XML文件,并且要以元素为根。
如何在配置类中引用xml配置?
使用@Import注解导入
或者采用一个更好的办法,创建一个更高级别的Config,在 这个类中使用@Import将两个配置类组合在一起
@ImportResource 用于导入指定的配置文件
在3.1版本中,Spring引入了bean profile的功能。要使用profile,你首 先要将所有不同的bean定义整理到一个或多个profile之中,在将应用 部署到每个环境时,要确保对应的profile处于激活(active)的状态。
在Java配置中,可以使用@Profile注解指定某个bean属于哪一个 profile。
Spring在确定哪个profile处于激活状态时,需要依赖两个独立的属 性:spring.profiles.active和 spring.profiles.default。
如果设置了 spring.profiles.active属性的话,那么它的值就会用来确定 哪个profile是激活的。但如果没有设置spring.profiles.active 属性的话,那Spring将会查找spring.profiles.default的值。 如果spring.profiles.active和spring.profiles.default 均没有设置的话,那就没有激活的profile,因此只会创建那些没有定 义在profile中的bean。
有多种方式来设置这两个属性:
- 作为DispatcherServlet的初始化参数;
- 作为Web应用的上下文参数;
- 作为JNDI条目;
- 作为环境变量;
- 作为JVM的系统属性;
- 在集成测试类上,使用@ActiveProfiles注解设置。
条件化的bean @Conditional注解
Spring 4引入 了一个新的@Conditional注解,它可以用到带有@Bean注解的方 法上。如果给定的条件计算结果为true,就会创建这个bean,否则 的话,这个bean会被忽略。
当确实发生歧义性的时候,Spring提供了多种可选方案来解决 这样的问题。你可以将可选bean中的某一个设为首选(primary)的 bean,或者使用限定符(qualifier)来帮助Spring将可选的bean的范围 缩小到只有一个bean。
如果将所 有的限定符都用上后依然存在歧义性,那么你可以继续使用更多的限 定符来缩小选择范围。@Qualifier注解是使用限定符的主要方式。它可以与@Autowired 和@Inject协同使用,在注入的时候指定想要注入进去的是哪个 bean。
bean的作用域
在默认情况下,Spring应用上下文中所有bean都是作为以单例 (singleton)的形式创建的。也就是说,不管给定的一个bean被注入到其他bean多少次,每次所注入的都是同一个实例。
Spring定义了多种作用域 , 使用 @Scope注解 绑定
- 单例(Singleton):在整个应用中,只创建bean的一个实例。
- 原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。
- 会话(Session):在Web应用中,为每个会话创建一个bean实 例。
- 请求(Rquest):在Web应用中,为每个请求创建一个bean实 例。
注入外部的值
Environment 的 getProperty() 获取值
Environment 的 getActiveProfiles():返回激活profile名称的 数组;
面向切面的Spring
创建切点来定义切面所织入的连接点是AOP框架的基本功能。
Spring提供了4种类型的AOP支持:
- 基于代理的经典Spring AOP;【虽然经典,但已经过时,基本被基于注解的方式取代】
- 纯POJO切面;
- @AspectJ注解驱动的切面;
- 注入式AspectJ切面(适用于Spring各版本)。
前三种都是Spring AOP实现的变体,Spring AOP构建在动态代理基础 之上,因此,Spring对AOP的支持局限于方法拦截。
4.1 通过切点来选择连接点
切点用于准确定位应该在什么地方应用切面的通知。
execution()指示器
使用within()指示器来限制匹配
bean()指示器
使用bean ID或bean名称作为参数来限制切点只匹配特定的bean。
使用注解创建切面
使用@Aspect 标志一个类,作为切面类
Spring使用AspectJ注解来声明通知方法
- @After 通知方法会在目标方法返回或抛出异常后调用
- @AfterReturning 通知方法会在目标方法返回后调用
- @AfterThrowing 通知方法会在目标方法抛出异常后调用
- @Around 通知方法会将目标方法封装起来
- @Before 通知方法会在目标方法调用之前执行
通过**@Pointcut**注解声明频繁使用的切点表达式
如果你使用JavaConfig的话,可以在配置类的类级别上通过使 EnableAspectJ-AutoProxy注解启用自动代理功能。
@DeclareParents注解由三部分组成:
- value属性指定了哪种类型的bean要引入该接口。在本例中,也 就是所有实现Performance的类型。(标记符后面的加号表示 是Performance的所有子类型,而不是Performance本 身。)
- defaultImpl属性指定了为引入功能提供实现的类。在这里, 我们指定的是DefaultEncoreable提供实现。
- @DeclareParents注解所标注的静态属性指明了要引入了接 口。在这里,我们所引入的是Encoreable接口。
xml配置的形式,这里不过多说明,可以查看原文
注入AspectJ切面
虽然Spring AOP能够满足许多应用的切面需求,但是与AspectJ相比, Spring AOP 是一个功能比较弱的AOP解决方案。AspectJ提供了Spring AOP所不能支持的许多类型的切点。
Spring Web应用程序
大多数基于 Java的Web框架都有着相同的逻辑,Spring MVC所有的请求都会通过一个前端控制 器(front controller)Servlet。前端控制器是常用的Web应用程序模式,在这里一个单实例的Servlet将请求委托给应用程序的其他组件来执行实际的处理。在Spring MVC中,DispatcherServlet就是前端控制器。
如图中所示,所有的请求将通过DispatcherServlet,在由处理器映射,把请求分发到对应的控制器(Controller层)去处理实际的业务代码,然后控制器(Controller层)对应返回模型(model,ModelAndView)对象,并通过视图解析器将其中的数据解析,映射到前端JSP中
按照传统的方式,像DispatcherServlet这样的Servlet会配置在 web.xml文件中,这个文件会放到应用的WAR包里面。当然,这是配置DispatcherServlet的方法之一。
AbstractAnnotationConfigDispatcherServletInitializer剖析
在Servlet3.0环境中,容器会在类路径中查找实现 javax.servlet.ServletContainerInitializer接口的类, 如果能发现的话,就会用它来配置Servlet容器。
Spring提供了这个接口的实现,名为SpringServletContainerInitializer,这个类实现了WebApplicationInitializer的接口。
Spring3.2引入了一个便利的WebApplicationInitializer接口基础实现,也就是AbstractAnnotationConfigDispatcherServletInitializer,所以我们只需要创建一个类继承AbstractAnnotationConfigDispatcherServletInitializer(同时也就实现了 WebApplicationInitializer),因此当部署到Servlet 3.0容器中的时候,容器会自动发现它,并用它来配置Servlet上下文。
继承AbstractAnnotationConfigDispatcherServletInitializer可以重写了三个方法:
- getServletMappings(),它会将一个或多个路径映射到DispatcherServlet上。比如,它映射的是“/”,这表示它会是应用的默认Servlet。它会处理进入应用的所有请求。 为了理解其他的两个方法,我们首先要理解DispatcherServlet和一个Servlet监听器(也就是ContextLoaderListener)的关系
- getServletConfigClasses()方法中,我们要求DispatcherServlet加载应用上下文时,使用定义在WebConfig配置类(使用Java配置)中的bean。方法返回的带有@Configuration注解的类将会用来定义DispatcherServlet应用上下文中的 bean。
- getRootConfigClasses()方法返回的带有@Configuration注解的类将会用来配置ContextLoaderListener创建的应用上下文中的bean。
启用Spring MVC
Spring是使用XML进行配 置的,你可以使用mvc:annotation-driven启用注解驱动的 Spring MVC。
的最简单的Spring MVC配置就是一个带 有@EnableWebMvc注解的类
但还有不少问题要解 决:
- 没有配置视图解析器。如果这样的话,Spring默认会使 用BeanNameView-Resolver,这个视图解析器会查找ID与视 图名称匹配的bean,并且查找的bean要实现View接口,它以这样 的方式来解析视图。
- 没有启用组件扫描。这样的结果就是,Spring只能找到显式声明 在配置类中的控制器。
- 这样配置的话,DispatcherServlet会映射为应用的默认 Servlet,所以它会处理所有的请求,包括对静态资源的请求,如 图片和样式表(在大多数情况下,这可能并不是你想要的效 果)
Spring提供了两种支持JSP视图的方式:
- InternalResourceViewResolver会将视图名解析为JSP文 件。另外,如果在你的JSP页面中使用了JSP标准标签库 (JavaServer Pages Standard Tag Library,JSTL)的 话,InternalResourceViewResolver能够将视图名解析为 JstlView形式的JSP文件,从而将JSTL本地化和资源bundle变量暴 露给JSTL的格式化(formatting)和信息(message)标签。
- Spring提供了两个JSP标签库,一个用于表单到模型的绑定,另一 个提供了通用的工具类特性。
自定义DispatcherServlet配置
添加其他的Servlet和Filter
最简单的方式就是实现 Spring的WebApplicationInitializer接口。
类似地,我们还可以创建新的WebApplicationInitializer实现 来注册Listener和Filter。
处理异常
Spring提供了多种方式将异常转换为响应:
- 特定的Spring异常将会自动映射为指定的HTTP状态码;
- 异常上可以添加@ResponseStatus注解,从而将其映射为某一 个HTTP状态码;
- 在方法上可以添加@ExceptionHandler注解,使其用来处理 异常。
如果控制器类的特定切面能够运用到整个应用程序的所有控制器中, 那么这将会便利很多。举例来说,如果要在多个控制器中处理异常, 那@ExceptionHandler注解所标注的方法是很有用的。不过,如 果多个控制器类中都会抛出某个特定的异常,那么你可能会发现要在 所有的控制器方法中重复相同的@ExceptionHandler方法。或 者,为了避免重复,我们会创建一个基础的控制器类,所有控制器类 要扩展这个类,从而继承通用的@ExceptionHandler方法。
Spring 3.2为这类问题引入了一个新的解决方案:控制器通知。控制器 通知(controller advice)是任意带有@ControllerAdvice注解的 类,这个类会包含一个或多个如下类型的方法:
- @ExceptionHandler注解标注的方法;
- @InitBinder注解标注的方法;
- @ModelAttribute注解标注的方法。
配置数据源
- 通过JDBC驱动程序定义的数据源;
- 通过JNDI查找的数据源;
- 连接池的数据源。
基于JDBC驱动的数据源
- DriverManagerDataSource:在每个连接请求时都会返回一 个新建的连接。与DBCP的BasicDataSource不同, 由DriverManagerDataSource提供的连接并没有进行池化管 理;
- SimpleDriverDataSource:与DriverManagerDataSource的工作方式类似,但是它直接 使用JDBC驱动,来解决在特定环境下的类加载问题,这样的环 境包括OSGi容器;
- SingleConnectionDataSource:在每个连接请求时都会返 回同一个的连接。尽管SingleConnectionDataSource不是 严格意义上的连接池数据源,但是你可以将其视为只有一个连接 的池。
配置缓存管理器
Spring 3.1内置了五个缓存管理器实现,如下所示:
- SimpleCacheManager
- NoOpCacheManager
- ConcurrentMapCacheManager
- CompositeCacheManager
- EhCacheCacheManager
Spring Data又提供了两个缓存管理器:
- RedisCacheManager(来自于Spring Data Redis项目
- GemfireCacheManager(来自于Spring Data GemFire项目)
使用RMI配置
创建过RMI服务,几个步骤:
- 创建一个继承于java.rmi.Remote的服务接口;
- 编写一个服务实现类,类中的方法必须抛出java.rmi.RemoteException异常;
- 运行RMI编译器(rmic),创建客户端stub类和服务端skeleton 类;
- 启动一个RMI注册表,以便持有这些服务;
- 在RMI注册表中注册服务。
在Spring中配置RMI服务
幸运的是,Spring提供了更简单的方式来发布RMI服务,不用再编写 那些需要抛出RemoteException异常的特定RMI类,只需简单地编 写实现服务功能的POJO就可以了,Spring会处理剩余的其他事项。
如果我们使用传统的RMI来发布此服务,SpitterService和 SpitterServiceImpl中的所有方法都需要抛出 java.rmi.RemoteException。但是如果我们使用Spring的 RmiServiceExporter把该类转变为RMI服务,那现有的实现不需 要做任何改变。
默认情况下,RmiServiceExporter会尝试绑定到本地机器1099端 口上的RMI注册表。如果在这个端口没有发现RMI注册 表,RmiServiceExporter将会启动一个注册表。如果希望绑定到 不同端口或主机上的RMI注册表,那么我们可以通过registryPort 和registryHost属性来指定。
装配RMI服务
传统上,RMI客户端必须使用RMI API的Naming类从RMI注册表中查 找服务。
Spring的RmiProxyFactoryBean是一个工厂bean,该bean可以为 RMI服务创建代理。
RMI是一种实现远程服务交互的好办法,但是它存在某些限制。首 先,RMI很难穿越防火墙,这是因为RMI使用任意端口来交互——这 是防火墙通常所不允许的。在企业内部网络环境中,我们通常不需要 担心这个问题。但是如果在互联网上运行,我们用RMI可能会遇到麻 烦。即使RMI提供了对HTTP的通道的支持(通常防火墙都允许), 但是建立这个通道也不是件容易的事。
另外一件需要考虑的事情是RMI是基于Java的。这意味着客户端和服 务端必须都是用Java开发的。因为RMI使用了Java的序列化机制,所 以通过网络传输的对象类型必须要保证在调用两端的Java运行时中是 完全相同的版本。对我们的应用而言,这可能是个问题,也可能不是 问题。但是选择RMI做远程服务时,必须要牢记这一点。
使用Hessian和Burlap发布远程服务
Hessian和Burlap是Caucho Technology提供的两种基于HTTP的轻量级远 程服务解决方案。借助于尽可能简单的API和通信协议,它们都致力 于简化Web服务。
Hessian,像RMI一样,使用二进制消息进行客户端和服务端的交互。 但与其他二进制远程调用技术(例如RMI)不同的是,它的二进制消 息可以移植到其他非Java的语言中,包括PHP、Python、C++和C#。
Burlap是一种基于XML的远程调用技术,这使得它可以自然而然地移 植到任何能够解析XML的语言上。正因为它基于XML,所以相比起 Hessian的二进制格式而言,Burlap可读性更强。但是和其他基于XML 的远程技术(例如SOAP或XML-RPC)不同,Burlap的消息结构尽可 能的简单,不需要额外的外部定义语言(例如WSDL或IDL)。
Spring的HttpInvoker
Spring开发团队意识到RMI服务和基于HTTP的服务(例如Hessian和 Burlap)之间的空白。一方面,RMI使用Java标准的对象序列化机 制,但是很难穿透防火墙。另一方面,Hessian和Burlap能很好地穿透 防火墙,但是使用私有的对象序列化机制。
就这样,Spring的HTTP invoker应运而生了。HTTP invoker是一个新的 远程调用模型,作为Spring框架的一部分,能够执行基于HTTP的远程 调用(让防火墙不为难),并使用Java的序列化机制(让开发者也乐 观其变)。使用基于HTTP invoker的服务和使用基于Hessian/Burlap的 服务非常相似。
因为HttpInvokerServiceExporter是一个Spring MVC控制器, 我们需要建立一个URL处理器
发布和使用Web服务
然Spring为使用Java API for XMLWeb Service(JAX-WS) 来发布和使用SOAP Web服务提供了大力支持
- 创建基于Spring的JAX-WS端点
- 在客户端代理JAX-WS服务
使用JMS发送消息
Java消息服务(Java Message Service ,JMS)是一个Java标准,定义 了使用消息代理的通用API。在JMS出现之前,每个消息代理都有私 有的API,这就使得不同代理之间的消息代码很难通用。但是借助 JMS,所有遵从规范的实现都使用通用的接口,这就类似于JDBC为 数据库操作提供了通用的接口一样。
- 首先安装mq
- 创建连接工厂
- 创建队列和交换机(主题)
- 绑定队列和交换机(主题)
- 使用RabbitTemplate发送和接收消息
借助Spring Boot简化
在Spring家族中,Spring Boot是令人兴奋(也许我敢说它是改变游戏 规则的)的新项目。它提供了四个主要的特性,能够改变开发Spring 应用程序的方式:
Spring Boot Starter:它将常用的依赖分组进行了整合,将其合并到一个依赖中,这样就可以一次性添加到项目的Maven或Gradle构建中;
自动配置:Spring Boot的自动配置特性利用了Spring 4对条件化配置的支持,合理地推测应用所需的bean并自动化配置它们;
命令行接口(Command-line interface,CLI):Spring Boot的CLI 发挥了Groovy编程语言的优势,并结合自动配置进一步简化 Spring应用的开发;
Actuator:它为Spring Boot应用添加了一定的管理特性。
模块:
- Core Container 核心模块:包含了: Core , Beans(创建和管理bean,IOC/DI) , Context , 和 Expression Language
- Data Access/Integration:包含了: JDBC , ORM(关系映射Api) , OXM (一个对Object/XML 映射实现的抽象层) , JMS (制造和消费的特征)和 Transaction (事务管理)
- WEB:web上下文模块建立在应用程序上下文之上,为基于web的应用程序提供了上下文。
- AOP:面向切面编程 AOP
- TEST:提供使用Junit 和 TestNG 对 Spring组件的支持
容器的基本实现
(1)Bean加载过程中核心类:DefaultListableBeanFactory 和 XmIBeanDefinitionReader
DefaultListableBeanFactory:该类是Spring注册以及加载Bean的默认实现。只有一个子类:XmlBeanFactory (已经被标志过时了)可以使用自定义的XML读取器XmIBeanDefinitionReader。
老版本的Spring是通过配置xml的形式配置Bean对象的,所以对于xml的解析就变得尤为重要,这里涉及的一个核心类就是:XML读取器类XmIBeanDefinitionReader
XmIBeanDefinitionReader中包含的重要对象:
- ResourceLoader:定义资源加载器,主要应用于根据给定的资源文件地址返回对应的Resource.
- BeanDefinitionReader:主要定义资源文件读取并转换为 BeanDefinition 的各个功能。EnvironmentCapable:定义获取 Environment 方法。
- DocumentLoader:定义从资源文件加载到转换为 Document 的功能AbstractBeanDefinitionReader: 对 EnvironmentCapable、BeanDefinitionReader 类定义的功能进行实现,
- BeanDefinitionDocumentReader:定义读取 Docuemnt 并注册 BeanDefinition 功能。
- BeanDefinitionParserDelegate: 定义解析 Element 的各种方法
XmIBeanDefinitionReader 会获取XML文件的验证模式(DTD与XSD),加载XML并解析成对应的Document,根据Document中的配置注册Bean信息。
(2)XML元数据信息加载后核心对象:BeanDefinition 接口
解析已经注册 BeanDefinition 接口
BeanDefinitionDocumentReader 是一个接口,方法registerBeanDefinitions 用于注册BeanDefinitions
实际处理注册BeanDefinitions是在BeanDefinitionDocumentReader 的默认实现是 DefaultBeanDefinitionDocumentReader 的 doRegisterBeanDefinitions 中的 parseBeanDefinitions 方法处理,并生成 BeanDefinitionHolder 对象,由**XmlReaderContext** 持有。
而**BeanDefinitionHolder** 中就存在 BeanDefinition 接口属性
(3)如何解析 BeanDefinition ?
也就是讨论:parseBeanDefinitions 方法
可以看到对于不同的标签采用不同的处理
大致的逻辑总结如下:
- 首先委托 BeanDefinitionDelegate 类的 parse BeanDefinition Element 方法进行元素解析,返回 BeanDefinitionHolder 类型的实例 bdHolder,经过这个方法后,bdFlolder 实例己经包含我们配置文件中配置的各种属性了,例如 class、name、id、alias 之类的属性。
- 当返回的 bdHolder 不为空的情况下若存在默认标签的子节点下再有自定义属性,还需要再次对自定义标签进行解析
- 解析完成后,需要对解析后的 bdHolder 进行注册,同样,注册操作委托给了 BeanDefinitionReaderUtils 的registerBeanDefinition 方法。
- 最后发出响应事件,通知想关的监听器,这个bean 已经加载完成了
解析和注册 BeanDefinition
其实就是将配置文件xml中的bean解析成BeanDefinition对象,然后被注册到 BeanDefinitionRegistry类型的实例中
扩展补充:
扩展 Spring 自定标签配置大致需要以下几个步骤:
- 创建一个需要扩展的组件
- 定义一个 XSD 文件描述组件内容
- 创建一个文件,实现 BeanDefinitionParser 接口,用来解析 XSD 文件中的定义和组件
- 定义创建一个Handler 文件,扩展自NamespaceHlandlerSupport;目的是将组件注册到 Spring容器
- 编写 Spring.handlers 和 Spring.schemas 文件
Bean 的加载
步骤:
- 转换对应beanName,就是先解析bean的别名,默认名称等
- 单例在Spring中只会存在一个对象,后续获取会先从缓存中获取,(尝试先从缓存中)(Spring创建bean,原则是不等bean创建完成就会先将创建bean的ObjectFactory提早曝光加入缓存)
- Bean的实例化
- 原型模式的依赖检查(单例要解决循环依赖的问题)
- 检测parentBeanFactory,判断当前的XML配置文件中是否有对应bean的配置,没有就需要getBean递归查找了
- 将存储xml配置文件的GernericBeanDefinition转换成RootBeanDefinition
- 寻找依赖,加载bean的时候,因为会用到对应的属性,这些属性有可能是动态加载的,所以就必须先加载对应的依赖属性
- 针对不同的scope进行bean的创建
- 类型转换,实际的bean可能是String类型,但是requireType传入的是Integer类型,这就会把String类型转换成对应requireType指定的类型。
几个重要类:
(1)FactoryBean 的使用
一般情况下,Spring 通过反射机制利用 bean 的 class 属性指定实现类来实例化 bean 。Spring 为此提供了一个 org, Springframework bean. factory. FactoryBcan 的工厂类接口,用户可通过实现该接口定制实例化 bean 的逻辑。
(2)AbstractAutowireCapableBeanFactory ****
AbstractAutowireCapableBeanFactory ****是一个抽象类,默认实现也是DefaultListableBeanFactor,很多获取bean,解析bean的实际工作都是它处理的,以及bean的前置处理和后置处理。
当 resolveBeforeInstantiation后,有两个选择,如果创建了代理或者说重写了InstantiationAwareBeanPostProcessor 的 postProcessBeforeInstantiation方法,并在方法中改变了bean,否则就直接返回了,所以这里如果需要我们可以通过重写来进行扩展。之后就是常规的创建bean了
Bean的创建
Bean的创建过程,其实就是通过bean工厂实例化bean,其实涉及BeanDefinition 转换成 BeanWrapper
- BeanDefinition 转换成 BeanWrapper
- MegedBeanDefinitionPostProcessor 的应用
- 依赖处理
- 属性填充
- 注册DisposableBean 一次性bean
比较重要的几个对象,可以看看:
RootBeanDefinition
补充:
(1)激活 Aware 方法
在分析其原理之前,我们先了解一下 Aware 的使用。Spring 中提供一些 Aware 相关接口,比如 BeanFactoryAware、ApplicationContextAware、 ResourceLoader Aware、 ServletContextAware 等实现这些 Aware接口的 Bean 在被初始之后,可以取得一些相对应的资源,例如实现BeanFactoryAware 的 Bcan 在初始后,Spring 容器将会注人 BcanFactory 的实例,而实现ApplicationContextAware 的 Bean,在 Bean 被初始后,将会被注人 ApplicationContext 的实例等。
(2)处理器的应用
BeanPostProcessor 相信大家都不陌生,这是 Spring 中开放式架构中一个必不可少的亮点,给用户充足的权限去更改或者扩展 Spring,而除了 BeanPostProcessor 外还有很多其他的PostProcessor,当然大部分都是以此为基础,继承自 BeanPostProcessor 。BeanPostProcessor 的使用位置就是这里,在调用客户自定义初始化方法前以及调用自定义初始化方法后分别会调用BeanPostProcessor 的 postProcessBeforeinitialization 和 postProcessAferlnitialization 方法,使用户可以根据自己的业务需求进行响应的处理。
(3) 激活自定义的 init 方法
客户定制的初始化方法除了我们熟知的使用配置 init-method 外,还有使自定义的Bean 实现InitializingBean 接口,并在afterPropertiesSet 中实现自己的初始化业务逻辑。
容器的功能扩展
Spring中还提供了另一个接口 ApplicationContext, 用于扩展BcanFacotry 中现有的功能。ApplicationContext 和 BeanFacotry 两者都是用于加载 Bean 的,但是相比之下,ApplicationContext 提供了更多的书展功能,简单一点说:ApplicationContext 包含 BcanFactory 的所有功能。
3.激活自定义的 init 方法
客户定制的初始化方法除了我们熟知的使用配置 init-method 外,还有使自定义的bean 实现InitializingBean 接口,并在alerPropertiesset 中实现自己的初始化业务逻辑。
容器的功能扩展
Spring中还提供了另一个接口 ApplicationContext, 用于扩展 BcanFacotry 中现有的功能。ApplicationContext 和 BeanFacotry 两者都是用于加载 Bean 的,但是相比之下,ApplicationContext 提供了更多的书展功能,简单一点说:ApplicationContext 包含 BcanFactory 的所有功能。
ClassPathXmlApplicationContext 初始化的步骤:
(1)初始化前的准备工作,例如对系统属性或者环境变量进行准各及验证。
(2)初始化 BcanFactory,并进行 XML 文件读取 (3)对 BeanFactory 进行各种功能填充@Qualifier 与@Autowired 应该是大家非常熟悉的注解,那么这两个注解正是在这一步骤中增加的支持。
(4子类覆盖方法做额外的处理。
(5)激活各种 BeanFactory 处理器。
(6)注册拦截bean 创建的 bean 处理器,这里只是注册,真正的调用是在 getBean 时候。(7)为上下文初始化 Message 源,即对不同语言的消息体进行国际化处理。
(8)初始化应用消息广播器,并放人“applicationEventMulticaster” bean 中。
(9)留给子类来初始化其他的 bean。
(10)在所有注册的 bean 中查找 listener bean, 注册到消息广播器中。
(11)初始化剩下的单实例(非惰性的)
(12)完成刷新过程,通知生命周期处理器 lifecycleProcessor 刷新过程,同时发出 ContextRefreshEvent 通知别人
AOP
- 注册或者升级 AnnotationAwareAspectJAutoProxyCreator
对于 AOP 的实现,基本上都是靠AnnotationAwareAspectJAutoProxyCreator 去完成,它可以根据@Point 注解定义的切点来自动代理相匹配的 bean。但是为了配置简便,Spring 使用了自定义配置来帮助我们自动注册 AnnotationAwareAspecJAutoProxyCreator,其注册过程就是在这里实现的。
动态代理
SpringMvC 解决的问题
(1)将Web 页面的请求传给服务器。
(2)根据不同的请求处理不同的逻辑单元。(3)返回处理结果数据并跳转至响应的页面。
ContextLoaderListener ,实现了 ServletContextListener 启动容器的时候,自动装配ApplicationContext 到 ServletContext。
ServletContext 域对象是服务器在内存上创建的存储空间,用于不同动态资源(servlet)之间传递和共享数据,是在项目初始化启动的时候创建的,假如 tomcat 里面运行了一个web应用,有三个servlet。他们共享一个数据(ServletContext),可以从这个数据区根据其作用范围拿到数据。
DispatcherServlet
在 Spring 中,ContextLoaderListener 只是辅助功能,用于创建 WebApplicationc实例,而真正的逻辑实现其实是在 DispatcherServlet 中进行的,DispatcherServlet 是接口的实现类。
servlet 的生命周期是由 servlet 的容器来控制的,它可以分为3个阶段:
1 初始
ServletConfig 对象包舍了servlet 的初始化配置信息
2 远行阶段
当 servlet 容器接收到一个请求时,servlet 容器会针对这个请求创建servletResponse 和servletRequest 对象;然后调用 service 方法。
3 销毁阶段
当 Web 应用被终止时,serviet 容器会先调用 servlet 对象的destrory方法,同时也会销毀与 servlet 对象相关联的 servletConfig 对象。
DispatcherServlet 的初始化
在servlet 初始化阶段会调用其 init 方法。DispatcherServlet 中是否重写了 init 方法。
- 封装及验证初始化参数ServletConfigProperty Values 除了封装属性外还有对属性验证的功能。
2.将当前 servlet 实例转化成 BeanWrapper 实例
PropertyAcalssorFactory.forBeanProperty Access 是 Spring 中提供的工具实例转化为 Spring 中可以处理的 Bcan Wrapper 类型的实例。
3.注册相对于 Resource 的属性编辑器
属性编辑器,我们在上文中已经介绍并且分析过其原理,这里使用属性对当前实例( Dispatcherservlet)属性注人过程中一旦遇到 Resource 类型ResourceEditor 去解析。
- 属性注入
BeanWrapper 为-Spring 中的方法,支持 Spring 的自动注人。其实我们最常用的属性注人无非是 contextAttribute、contextClass、nameSpace、contextConfigLocation 等属性。5. servletBean 的初始化
在 ContextL.oaderListener 加载的时候己经创建了 WebApplicationContext 实例,而在这个西数中最重要的就是对这个实例进行进一步的补充初始化。
WebApplicationContext 初始化
Dispatcherservlet 的逻辑处理
根据之前的系例,我们知道在Httpservlet 类中分别提供了相应的服务方法,doDeleteO、 doGeto 、 doOptionsO doPostO、doPutO和 doTraceO,他会根据不同形式将程序引导至对应的函数进行处理。
函数中已经开始把细节转移到了 doService 函数中实现,但是我们不难看出处理请求前后所做的准备与处理工作。
(1)为了保证当前线程的 LocaleContext 以及 RequestAttributes 可以在当前请求后还能恢复,提取当前线程的两个属性。
(2)根据当前 request 创建对应的 LocaleContext 和 RequestAttributes,并绑定到当前线程。
(3)委托给 doService 方法进一步处理。(4)请求处理结束后恢复线程到原始状态。(5)请求处理结束后无论成功与否发布事件通知。继续查看 doService 方法。
doDispatch 函数中展示了 Spring 请求处理所涉 及的主要逻辑
Handlerlnterceptor 的处理
Servlet API 定义的servlet 过滤器可以在 servlet 处理每个 Web 请求的前后分别对它进行前置处理和后置处理。每个处理拦截都必须实现 Handlerinterceptor 接口,它包含三个需要你调用的方法:preHlandle0: postHandle0和 afterCompletion0。第一个和第二个方法分别是序处理请求之前而之后被调用的。第二个方法还允许访问返回的 ModlAndView 对象。
WebApplicationContext 的初始化initWebApplicationContext 两数的主要工作就是创建或刷新 WebApplicationContext实里