最近在对公司框架进行二次改造封装运用到了很多spring生命周期相关的东西,复习整理一下,顺便总结一下相关的使用。
SpringIOC
IOC介绍
IOC的重要性
Spring的IOC可以说是Spring最核心的功能,也是整个Spring体系的基石;基于IOC衍生出了许多强大的功能组件,即使AOP的成功也得益于IOC的容器管理,至于更上层的组件,Spring Data、Spring WEB、Spring TX、Spring JMS等这些都是在IOC容器的基础上开发。
IOC,主要的功能是用来管理应用的实例对象,包括对象生命周期管理、依赖管理、控制反转等。
以上是我从网上找到一个Spring结构图,可以体现出底层的Core Container就是整个Spring应用的基石。
Spring之所以如此成功也跟IOC的设计有关,目前来看大多数Java框架或者组件都可以交给IOC管理,让其能够进入融入到Spring体系中,让其统一管理。最开始的Spring就跟一个大房子一样,只要注册后,都可以进入Spring这个房子,在这个体系内组件可以相互使用,从整合角度来说它就像一个胶水框架,能够黏住需要实例化的Java组件,将生命周期和组件依赖等相关职责交给IOC管理。后面基于这个理念,又延伸出了SpringBoot,在具备Spring原有的强大包容性的基础上,对各个常用组件进行了二次封装,简化了大量组件的初始化配置,以及整合步骤,让其开发者能够更加方便的使用。
Spring使用之IOC
现在好多一来都直接使用SpringBoot依赖一引入,包自动生成点一下启动就可以启动一个Spring应用。
如以下这种:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
这种模式确实降低了学习成本,提高了易用性,但是也屏蔽了大量细节底层,对于初学者来说少了很多整合的弯路,也就是少了很多毒打,从而导致出现了某些疑难杂症都不知道从何下手,不便于研究其原理。
作为一个使用Spring从SSH到现在的SpringCloud的程序员,一路看着Spring高速发展,惊喜于Spring每次的迭代,从以前整合个SSH、SSM,因为框架版本问题导致适配各种问题加班熬夜历历在目,所以得感谢SpringBoot和现在的Cloud,为Java程序员入门大大降低了门槛。
好了,缅怀完了,接下来,正式从Spring的使用分析IOC体系和原理。
基于XML装配Bean
ClassPathXmlApplicationContext cpxApp =
new ClassPathXmlApplicationContext("classpath:spring.xml");
Object user = cpxApp.getBean("user");
System.out.println(user);
spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.example.demo.entity.User">
</bean>
</beans>
最开始Spring ApplicationContext的使用方式,在SSH,SSM打天下的时代,这种应用特别多,提倡配置大于约定,因为那时候还没啥约定,生态不像现在这么成熟,所以bean都在xml中声明,依赖等也得通过配置属性注入。部署都时候结合配置Spring提供的 ContextLoaderListener配置在web.xml中自动创建spring应用,即可完成一个部署在tomcat里面的spring应用。
基于注解装配Bean
后来在注解流行后,又诞生以注解为主的ApplicationContext
@Configuration
public class SpringIOCDemo {
@Bean
public User user(){
return new User();
}
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationApp = new AnnotationConfigApplicationContext(SpringIOCDemo.class);
Object user = annotationApp.getBean("user");
System.out.println(user);
}
}
可以看到AnnotationConfigApplicationContext这种方式,主要依赖于Spring提供的相关注解,进行装载,如@Bean。这种方式更加清晰明了,不用写冗余的xml配置文件,因为xml文件,需要编写大量的重复标签,如:bean、property等,而且如果在IDE不智能的情况下bean注入出错,写错等问题时常发生,这也是为什么现在逐渐淘汰了xml配置的原因。
这也是目前Spring的主要应用装载方式,如SpringBoot我们可以看到@SpringBootApplication注解标识启动程序,也可以大致猜出了SpringBoot也是基于注解启动装载的,但是SpringBoot更加复杂点,有好几种,只能说AnnotationConfigApplicationContext是SpringBoot的默认ApplicationContext,Web应用通常不是它,但是也异曲同工。
SpringBoot的ApplicationContext
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch(this.webApplicationType) {
case SERVLET:
contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
break;
case REACTIVE:
contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
break;
default:
contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
}
} catch (ClassNotFoundException var3) {
throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
}
}
return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
}
可以看到,虽然不是使用的AnnotationConfigApplicationContext,但是其它两种也是带Annotation开头的,都是获取注解,因此现在Spring应用注解使用为主流。
ApplicationContext
前面介绍了主流的两种ApplicationContext的使用,也反应了Spring几年的发展历程。可以看到ApplicationContext是Spring启动装载的入口也是核心,ApplicationContext就是SpringIOC的主要实现。
可以看到ApplicationContext还有很多的实现,可以根据不同场景下选择相应的ApplicationContext创建Spring应用,常用的就是ClassPathXmlApplicationContext、FileSystemXmlApplicationContext、AnnotationConfigApplicationContext这几种,反正我手动操作创建过的就这三种。
ApplicationContext体系结构
在分析体系结构前要先明确ApplicationContext是用来干嘛的,在Spring应用中承担了什么样的角色,从上文分析来看初始化一个启动一个Spring应用就是new一个xxxApplicationContext因此可以大致总结出,ApplicationContext就是Spring的核心,入口,也可以说这就是Spring框架的具体实现。
ApplicationContext就是SpringIOC,那么它是不是也就具备了Spring的能力,Spring的主要能力是管理bean也就是我们说的beanFactory,管理bean的依据是什么呢?这个就是具体的实现为准,比如ClassPathXmlApplicationContext就是以classpath下的xml作为spring容器初始化依据,AnnotationConfigApplicationContext就是以标识了spring容器注解的类作为依据。明白了这两点,再来看ApplicationContext的类图就非常清晰明了了。
可以看到ApplicationContext是一个接口并且都继承了BeanFactory和ResourceLoader。按照接口的定义来看,要实现一个ApplicationContext需要提供BeanFactory的能力,也就是我们常用的getBean的相关方法,因为bean的创建不是凭空出现的,需要有依据,也就是我们说的配置,因此必须要指定入口,也就有了ResourceLoader接口。这时候再回过头看那么多的ApplicationContext就能够一下分析出其体系,它们只是按照相应配置加载方式不同提供了不同的实现而已。
ApplicationContext通常主要有两大子类实现,分别是AbstractRefreshableApplicationContext和GenericApplicationContext。
AbstractRefreshableApplicationContext
AbstractRefreshableApplicationContext是一偏向垂直抽象扩展ApplicationContext,它只支持ApplicationContext的功能,更加关注垂直扩展中,BeanDefinition的来源,提供了一个重要抽象方法loadBeanDefinitions用来加载BeanDefinition。适用于应用创建的时候就已经确定了所有bean,没有动态扩展。如ClassPathXmlApplicationContext和FileSystemXmlApplicationContext这种ApplicationContext,在应用创建的时候就已经完成了bean的声明,更加关注容器的刷新与创建。
通常使用就是重写loadBeanDefinitions方法:
ClassPathXmlApplicationContext cpxApp = new ClassPathXmlApplicationContext("classpath:spring.xml");
// ClassPathXmlApplicationContext#ClassPathXmlApplicationContext(java.lang.String[], boolean, org.springframework.context.ApplicationContext)
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
super(parent);
// 设置beanDefinition来源
this.setConfigLocations(configLocations);
if (refresh) {
this.refresh();
}
}
// AbstractXmlApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.xml.XmlBeanDefinitionReader)
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = this.getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
// 获取ClassPathXmlApplicationContext设置的xml
String[] configLocations = this.getConfigLocations();
if (configLocations != null) {
// 读取xml,并将bena注册进beanFactory中
reader.loadBeanDefinitions(configLocations);
}
}
GenericApplicationContext
AbstractRefreshableApplicationContext它相对于GenericApplicationContext最大的区别在于GenericApplicationContext实现了BeanDefinitionRegistry接口并且GenericApplicationContext是一个实现类,可以直接使用,提供了默认bean注册到Beanfactory。
从扩展性来看,我们要自己实现一个定制化的Applicationcontext,使用AbstractRefreshableApplicationContext必须要创建个实现类,来继承它,自己手动实现将BeanDefinition注册到beanFactory中。而GenericApplicationContext持有一个DefaultListableBeanFactory实例,实现了BeanDefinitionRegistry的能力,能够直接使用。
GenericApplicationContext ctx = new GenericApplicationContext();
// 使用任意的Reader
XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(ctx);
xmlReader.loadBeanDefinitions(new ClassPathResource("spring.xml"));
// 多次加载beanDefinition
PropertiesBeanDefinitionReader propReader = new PropertiesBeanDefinitionReader(ctx);
propReader.loadBeanDefinitions(new ClassPathResource("spring-ext.properties"));
ctx.registerBean(User.class);
//调用Refresh方法
ctx.refresh();
可以看到上面的使用,直接通过组合的方式就能够直接创建一个Applicationcontext并且支持多次加载,支持动态注册bean。比较有代表性的就是SpringBoot的ServletWebServerApplicationContext就是继承该类实现的。
自己实现一个ApplicationContext
了解了上面的ApplicationContext我们完全可以定制化一些自己的ApplicationContext。
比如我们曾经有个很骚的操作,就是为了集中管控某些组件,需要将实例化这些组件的xml给放到类似于现在分布式配置中心的一个远程应用中集中管理,然后各个应用在启动的时候在远程配置中心中读取到xml进行Spring容器启动,这将相当于本地没有xml,xml需要从远程读取,然后启动Spring应用。这时候咋个办呢,很明显Spring并没有提供这种能力,那么我们在了解了整个体系后,可以很简单的实现这个功能,
ClassPathXmlApplicationContext和FileSystemXmlApplicationContext声明:
public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext
public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext
参考本地的两种xml实现,即可写出如下伪代码:
public class RemoteXmlApplicationContext extends AbstractXmlApplicationContext {
public RemoteXmlApplicationContext() {
}
public RemoteXmlApplicationContext(ApplicationContext parent) {
super(parent);
}
public RemoteXmlApplicationContext(String configLocationRemoteAddr) throws BeansException {
// 读取远程xml到本地
String[] configLocations = readXmlFileToLocation(configLocationRemoteAddr);
// super(null);
this.setConfigLocations(configLocations);
this.refresh();
}
}
这样就实现了一个通过读取远程xml来创建Spring应用的ApplicationContext,这个代码比较简单是因为AbstractXmlApplicationContext已经实现了xml解析装配等。如果要实现一个Spring不支持的存储类型的,需要自己重写loadBeanDefinitions方法将要实例化的bean初始化成BeanDefinition,什么是BeanDefinition就不介绍了,这是入门知识了。
可以通过数据库、txt、二进制等任意类型的存储方式来进行创建你想要的Spring应用,这都是基于ApplicationContext体系的。