4.1 Spring核心组件
Last updated
Last updated
Spring框架的核心包括IOC、AOP以及辅助工具SpringEL等。
首先要明确一个概念,Spring的IOC容器是ApplicationContext。常用的ApplicationContext如下:
ClassPathXmlApplicationContext: 从ClassPath路径下加载XML配置的上下文。
FileSystemXmlApplicationContext:从文件系统中加载XML配置的上下文。
XmlWebApplicationContext:Web开发中从XML中记载Web上下文,区别于上面之处在于此上下文是基于ServletContext的。
AnnotationConfigWebApplicationContext:从注解类中加载Web上下文。
对于这些上下文中的实例在Spring中被叫做Bean。一个Bean的生命周期管理如下图所示:
图中所示为Spring中的几个关键类、接口以及对应的方法,箭头的方向表示执行的顺序,描述了一个Bean从开始初始化到销毁会经历的一些方法调用。
Spring中的ApplicaitonContext是父子层次结构的,存在多个上下文的时候,会有一个根上下文做为其他上下文的父亲。
如上,如果在web.xml中使用Listener监听器来加载Spring的配置,Spring会创建一个全局的WebApplicationContext上下文,称为根上下文,保存在 ServletContext中,key是WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
属性的值。可以使用工具类取出上下文:WebApplicationContextUtils.getWebApplicationContext(ServletContext)
或者WebApplicationContextUtils.getRequiredWebApplicationContext(ServletContext)
。
而很多情况下,我们还会配置一个或者多个DispatcherServlet,每个DispatcherServlet有一个自己的WebApplicationContext上下文。这个上下文是私有的,继承了根上下文中所有东西。保存在ServletContext中,key是 "org.springframework.web.servlet.FrameworkServlet.CONTEXT" + Servlet名称
。当一个Request对象产生时,会把这个WebApplicationContext上下文保存在Request对象中,key是DispatcherServlet.class.getName() + ".CONTEXT"
。可以使用工具类取出上下文:RequestContextUtils.getWebApplicationContext(request)
或者WebApplicationContextUtils.getWebApplicationContext(servletContext,attrname)
。
为了避免双亲上下文,可以不使用Listener监听器来加载Spring的配置,直接改用DispatcherServlet来加载Spring的配置。
Spring中提供了事件机制,用于监听容器事件的发生,在事件发生时做一些处理工作。
容器事件监听器:实现ApplicationListener接口的类, 可以监听容器的事件,包括:
ContextStartedEvent: 上下文启动事件
ContextRefreshedEvent:上下文刷新完毕事件
ContextStoppedEvent:上下文停止事件
ContextClosedEvent:上下文关闭事件
具有意识的Bean:形如xxAware的接口,实现了此种接口的Bean即为有意识,能够注入/获取到xx代表的事物。如,实现ApplicationContextAware接口的类,会自动注入当前的ApplicationContext。
此外,在Bean生命周期图中的BeanNameAware和BeanFactoryAware也是两个具有意识的Bean。
实现了BeanNameAware的Bean能够感知到自己在BeanFactory中注册的名称。
实现了BeanFactoryAware的bean能够感知到自己所属于的BeanFactory。
如果想要在应用初始化的时候做一些初始化方法,可以使用以下几种初始化方式:
直接在Bean的构造方法里做初始化工作。
使用@PostConstruct注解,指明在Bean构造器方法执行后执行的方法。
Bean实现InitializingBean接口,在afterPropertiesSet做初始化工作。
在XML中使用init-method指定Bean构造完成后调用的方法。
需要注意的是方法1和2并不保证在执行时依赖的其他Bean已经注入进来。因此如果想在某些Bean初始化完毕并注入进来之后再进行初始化工作,可以配合使用@DependsOn注解。
也可以使用BeanFactoryPostProcessor和BeanPostProcessor来做一些更为前置的初始化工作, 典型的应用场景就是实现自己的注解。
实现BeanFactoryPostProcesso接口可以在在Spring容器加载了Bean的定义之后,在Bean实例化之前执行,能够修改Bean的定义属性。如可以把Bean的scope从singleton改为prototype,也可以把property的值给修改掉。可以通过接口的参数获取到相关Bean的定义信息。
实现BeanPostProcesso接口可以在Spring容器实例化Bean之后,在执行Bean的初始化方法前后,添加一些自己的处理逻辑。Spring内置了几个BeanPostProcessor实现:
CommonAnnotationBeanPostProcessor:支持@Resource注解的注入
RequiredAnnotationBeanPostProcessor:支持@Required注解的注入
AutowiredAnnotationBeanPostProcessor:支持@Autowired注解的注入
ApplicationContextAwareProcessor:用来为bean注入ApplicationContext等容器对象
根据Bean的生命周期。以上提到的初始化方法的优先级为: BeanFactoryPostProcessor > Constructor > BeanPostProcessor.postProcessBeforeInitialization > @PostConstruct > InitializingBean > init-method。
此外,如果是想要在所有Bean都初始化完毕后做一次初始化工作,那么可以使用上一节所说的ApplicationListener,监听ContextRefreshedEvent。
而要在销毁Bean之前做一些收尾工作,有以下三种方式:
使用@PreDestroy注解,指明在容器关闭后执行的方法。
实现DisposableBean接口,在destroy方法做销毁工作。
在XML中使用destroy-method指定bean销毁时调用的方法。
根据Bean生命周期,可知优先级为:@PreDestroy > DisposableBean > destroy-method。
当要创建的Bean不能直接通过构造方法、setter方法、字段注入完成,还需要做一些初始化工作的时候,普通创建Bean的方式就力不从心了。 Spring提供了三种方式解决这个问题:
定义Bean时,指明factory-bean和factory-method
定义Bean直接使用类的静态方法
实现FactoryBean接口
针对第3种方式的FactoryBean,Spring自带了几个实现。:
PropertyPathFactoryBean: 用来获取目标Bean的属性值(实际是其getter方法的返回值)。PropertyPathFactoryBean返回值(基本数值类型或者对象)在最外层,则把返回值注册为容器中一个Bean,Bean名字为id 。
FieldRetrievingFactoryBean:用来获取类的静态属性值或者对象的实例属性值。
上面即获取类java.sql.Connection的静态属性值TRANSACTION_SERIALIZABLE。
MethodInvokingFactoryBean:用来调用类的静态方法或者调用Bean对象的实例方法。若有返回值则可注册为容器中的Bean或者作为依赖注入其它Bean中。
同样的,可以用inner Bean的方式将上面的FactoryBean构造的实例注入到其他bean中:
当然,现在Spring提供了@Bean注解,完全可以在代码中做这种动态生成Bean的工作。
对于在XML中已经存在的Bean注入List、Map等集合这种需求,可以这样配置:
注入枚举、静态值
对于枚举和静态值的注入,使用FieldRetrievingFactoryBean是一种办法。但其实当注入的字段值属于此字段对应类的静态字段时,只需要注明静态字段名即可。如:
AOP(Aspect Oriented Programming)是为了解决某些场景下代码重复问题的一种编程技术,允许程序模块化横向切割关注点或横向切割典型的责任划分。能够封装多个类中不同单元的相同功能,把应用业务逻辑和系统服务分开,经常用在日志和事务管理上。
说到AOP不得不提的就是Aspectj这个AOP框架,它是一种编译期AOP框架(即在编译的时候对被代理的类进行增强)。它自己有一套AOP各个组件的概念定义。其中的几个关键的概念如下:
方面:Aspect,横切多个类的某个功能描述。比如,一个日志模块可以被称作日志的AOP切面。
连接点:JointPoint, 程序执行过程中的某个函数调用。代表一个应用程序的某个位置,在这个位置我们可以插入一个AOP切面,它实际上是个应用程序执行Spring AOP的位置。
通知: Advice, 是个在方法执行前或执行后要做的动作,实际上是程序执行时要通过SpringAOP框架触发的代码段。Spring切面可以应用五种类型的通知:
before:前置通知,在一个方法执行前被调用。
after: 在方法执行之后调用的通知,无论方法执行是否成功。
after-returning: 仅当方法成功完成后执行的通知。
after-throwing: 在方法抛出异常退出时执行的通知。
around: 在方法执行之前和之后调用的通知。
切入点: PointCut, 是映射到一个或一组连接点的指示符,通知将在这些位置执行。可以通过表达式或匹配的方式指明切入点。Spring AOP支持的AspectJ切入点指示符如下:
execution:用于匹配方法执行的连接点;
within:用于匹配指定类型内的方法执行;
this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配;
target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;
args:用于匹配当前执行的方法传入的参数为指定类型的执行方法;
@within:用于匹配所以持有指定注解类型内的方法;
@target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;
@args:用于匹配当前执行的方法传入的参数持有指定注解的执行;
@annotation:用于匹配当前执行方法持有指定注解的方法;
bean:Spring AOP扩展的,AspectJ没有此指示符,用于匹配特定名称的Bean对象的执行方法;
reference pointcut:表示引用其他命名切入点,只有@ApectJ风格支持,Schema风格不支持。
其中类型匹配的语法如下所示:
匹配类型表达式:注解? 类的全限定名字
;匹配方法执行表达式:注解? 修饰符? 返回值类型 类型声明?方法名(参数列表) 异常列表?
此外,多个切入点表达式是可以组合的,AspectJ使用 且(&&)、或(||)、非(!)来组合切入点表达式,为了方便xml配置,Spring AOP 提供了and、or、not来代替&&、||、!。
需要注意的是Spring AOP是一种运行期的AOP框架,对Aspectj的支持只是使用了其定义的各个组件定义和注解,底层实现和它并没有多少关系。
使用Spring AOP有三种方式:
Spring AOP接口
此种方式基于Spring提供的aop接口:
前置通知:MethodBeforeAdvice
后置通知:AfterReturningAdvice
环绕通知:MethodInterceptor
异常通知:ThrowsAdvice
针对这些接口,进行实现并配置即可。此种方式需要对每一个被代理的bean创建一个ProxyFactoryBean并手动指定目标对象与通知,比较繁琐,笔者也不推荐使用,因此不再详细叙述。
依赖于Aspectj的XML配置
Spring从2.0开始开始支持Aspectj的XML配置方式,在aop:config标签下使用aop:pointcut、aop:advisor、aop:aspect配置,自动完成动态代理工作,大大简化了aop的开发工作。如下:
依赖于Aspectj注解的AOP配置
Spring现在也支持使用Aspectj的注解来做AOP,比起XML配置的方式,更加简单。
Spring中AOP的实现原理包括两种方式:JDK动态代理和CGLIB。
如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP。
如果目标对象实现了接口,可以强制使用CGLIB实现AOP。
如果目标对象没有实现了接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换。
也可以强制AOP使用CGLIB,如下
XML是Spring一开始就支持的配置方式,也是最主流的配置方式。从Spring 2.0开始,基于文件的配置就从DTD转换到了XSD,目的就是让大家能够更好地利用XSD做到配置的灵活性和便利性。这里首先要注意的是在加载XML文件时,通常会有前缀classpath:和者classpath*:这俩的区别在于当classpath中存在同样路径的多个文件时,前者只会去加载第一个找到的资源,而后者则会去加载所有找到资源。路径的对照表如下:
classpath:
classpath:applicationContext.xml
从classpath中加载
file:
file:/data/applicationContext.xml
作为 URL 从文件系统中加载。
http:
http://host/applicationContext.xml
作为 URL 加载。
(none)
/data/applicationContext.xml
根据上下文的不同而不同: FileSystemXmlApplicationContext对应文件系统;GenericWebApplicationContext对应ServletContext中的资源;ClassPathXmlApplicationContext对应Classpath下的资源。
之前IOC和MVC两章都有一些基本的Spring xml配置示例,这里主要讲解一些可能不是所有人都知道的知识点。首先看下面的一个配置示例:
对上述示例配置的说明如下:
xsi:schemaLocation:是为了指出XSD文件可以在本地jar包中找到,无须去网络位置获取。
p:xx:对属性赋值的简化,作用于属性注入。
c:xx:构造注入的简化。
使用<null/>
引用null值。
从Spring3开始,beans多了一个profile属性可以根据不同的profile使用不同的bean,可以用在本地、测试、生产环境使用不同的日志、数据库配置这种场景下。上面的bean2即表示在profile为dev时,才会初始化这个bean。其中,profile通过spring.profiles.active和spring.profiles.default这俩Java运行属性或者SPRING_PROFILES_DEFAULT和SPRING_PROFILES_ACTIVE环境变量来指定。此外,对于ContextLoaderListener,在web.xml可以配置其默认profile:
对于DispatchServlet,在web.xml中配置:
除了beans之外,spring还提供了很多额外的namespace以简化配置。
context: 此namespace下的配置主要是对上下文的配置简化。
annotation-config:开启注解装配。隐式地向Spring容器注册AutowiredAnnotationBeanPostProcessor、RequiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor以及PersistenceAnnotationBeanPostProcessor这4个BeanPostProcessor。
component-scan: 指定注解扫描的包路径。此配置包含了annotation-config的功能,因此如果配置了component-scan,那么annotation-config是可以省略的。
property-holder:根据指定的location扫描properties文件以及系统属性和环境变量,存储里面的键值对, 在XML配置里可以使用占位符${}引用。在代码中,可以使用注解@Value + 占位符引用。
util:此namepace下主要是提供了一些构建Bean的工具。
constant:根据表达式创建指定静态字段作为bean,可替代FieldRetrievingFactoryBean的使用。
property-path: 获取指定path的属性作为bean,可替代PropertyPathFactoryBean的使用。
properties: 从指定位置加载properties文件,创建一个java.util.Properties实例bean。后面就可以引用此bean的属性。可替代PropertiesFactoryBean的使用。
list:创建一个列表bean。可替代ListFactoryBean的使用。
map:创建一个map bean。可替代MapFactoryBean的使用。
set:创建一个set bean。可替代SetFactoryBean的使用。
mvc
annotation-driven: 开启MVC的注解支持,创建了和MVC、注解相关的一系列Bean,如RequestMappingHandlerMapping、RequestMappingHandlerAdapter等。
message-convertors: annotation-driven的子元素,用来配置HttpMessageConvertor。
argument-resolvers: annotation-driven的子元素,用来配置项目用到的自定义HandlerMethodArgumentResolver。
interceptors:配置项目用到的拦截器
content-negotiation: view-resolvers的子元素,配置内容协商视图。
现在利用注解+代码配置变得越来越流行。基本上对应着每一个XML配置都有相应的注解配置与之对应。常用的注解配置如下:
@Configuration: 表示此类的用途是用来定义Bean的。
@Bean:注解到方法上,创建、初始化一个Bean。对应于配置。
@Profile:对应于, 指定某个profile激活的Bean等。
@ComponentScan:对应于context:component-scan配置,扫描对应的包。
@EnableWebMVC: 对应于mvc:annotation-driven配置,开启MVC注解支持。配合@Configuration使用。被注解的对象实现WebMvcConfigurer接口(一般继承WebMvcConfigurerAdapter即可)可以自定义配置拦截器、视图协商器、MessageConverters等等。
@Import: @Import允许将其它JavaConfig形式的配置类引入到当前的@Configuration标注的配置类当中,对应于原来XML中的 ,也可以通过@ImportResource将XML形式定义的配置引入当前JavaConfig形式的配置类当中。
@PropertySource:配合@Configuration使用,用来加载.properties内容到Environment,比如: @PropertySource("classpath:config.properties"),需要容器中配置一个PropertySourcesPlaceholderConfigurer。
一个配置示例如下: