leap框架的启动过程和启动扩展

Nov 3, 2016


前言

leap的启动过程是一个比较复杂的过程在开始之前,我们需要先大致了解一下leap的启动需要做些什么事情。

实际上,leap的启动分为两大类:

  • 单例启动
  • 外部启动

所谓单例启动,就是leap自己以一个进程的方式启动,这种启动一般用于普通的java应用。 外部启动,就是通过外部的应用来启动leap,这种方式启动就比较多种多样了,一般来说我们需要做一个web应用的话,会由tomcat来启动leap,当然也可以已其他的容器比如netty来启动leap。

多数情况我们还是用tomcat或者其他web容器来启动leap,并且作为一个web应用对外提供http服务,目前在品高的api网关中,也使用netty来启动leap,同样可以对外利用netty的能力对外提供http服务。

本文我们会以leap的单例启动为主分析leap的整个启动过程和提供的扩展接口,另外简单分析以web的方式在外部启动leap的过程。

限于篇幅,本文不具体说明每个扩展接口如何使用,需要扩展的话可以找到扩展点对应的源码简单读一下。

单例启动

单例启动leap我们一般用如下代码:

AppContext.initStandalone();

这行代码会直接启动leap,启动过程首先检查是否已经启动过,如果已经启动过是不能再启动的。

然后按照如下三个阶段开始启动leap:

  • 配置读取和解析
  • 类加载和类的字节码增强
  • 加载所有Bean对象
  • 启动完成

这里的每个阶段都有自己要做的事情,也提供了扩展接口。


配置读取和解析

leap中加载配置的代码如下:

AppConfigSource cs = Factory.newInstance(AppConfigSource.class);
config = cs.loadConfig(null, null);

加载配置分四步:

  • 加载配置前
  • 加载property
    • 加载内部property
    • 加载外部proporty
    • property预处理
  • 加载其他配置
  • 加载配置后

加载配置前后的扩展

这里加载配置前和加载配置后预留了接口提供扩展:

package leap.core.config;

public interface AppConfigInitializable {
    default void preLoadConfig(AppConfigContext context, AppResources appResources){}
    default void postLoadConfig(AppConfigContext context, AppResources appResources){}
}

只需要实现这个接口,自行决定加载前需要做什么事情,加载后需要做什么事情即可。

注: 在加载配置的阶段,所有bean都没有初始化,因此不能使用注解的方式得到bean对象,leap支持使用服务加载机制(ServiceLoader)来初始化我们的类。在工程的resources目录下创建META-INF文件夹,再在META-INF目录下创建services目录,然后创建一个文本文件,文件名为要扩展的接口全名,文件内容为class=${实现类全名}

如:

src/main
    - java
        - app.initializable
                - MyClass.java
    - resources
        - META-INF
            - services
                leap.core.config.AppConfigInitializable

其中leap.core.config.AppConfigInitializable文件的内容为:

class=app.initializable.MyClass

这样配置之后leap就会在启动的时候自动初始化这个bean了,然后在对应的阶段执行这个方法了。

加载property过程的扩展

在加载property的过程,分为本地property和外部property,这两个阶段是有区别的:

  • 加载本地property的时候,可能需要对本地的配置做一些变动
  • 加载外部property的时候,property的来源是不限制的,可以从数据库或者网络等其他地方读取

加载本地配置,leap提供了相同的扩展接口leap.core.config.AppPropertyReader

package leap.core.config;
import leap.lang.resource.Resource;

public interface AppPropertyReader {
    boolean readProperties(AppPropertyContext context, Resource resource);
}

实现这个接口之后,同样需要使用服务加载机制来初始化扩展类。

加载外部配置,leap提供了扩展接口:

package leap.core.config;

public interface AppPropertyLoader {

    void loadProperties(AppPropertySetter props);

}

外部配置加载可以使用xml配置的方式配置外部加载器,如:

<config xmlns="http://www.leapframework.org/schema/config">
    <loader class="leap.core.config.loader.JdbcPropertyLoader" sort-order="1">
        <property name="driverClassName" value="${h2.driverClassName}"/>
        <property name="jdbcUrl"         value="${h2.jdbcUrl}"/>
        <property name="username"        value="sa"/>
        <property name="sql">
            select key_ key, value_ value from sys_config
        </property>
    </loader>
</config>

property加载完成之后,leap还预留了property预处理的扩展接口:

package leap.core.config;

import leap.lang.Out;

public interface AppPropertyProcessor {
	boolean process(String name, String value, Out<String> newValue);
}

这个处理器可以在读取完全部的配置之后,对property进行使用前的预处理,比如你配置的可以是一个加密的字符串,防止明文配置被人看到数据库密码一类的,可以在这里进行解密。

加载其他配置的扩展过程

加载其他配置的过程,leap提供了扩展接口:

package leap.core.config;

import leap.lang.resource.Resource;

public interface AppConfigReader {

    boolean readConfig(AppConfigContext context, Resource resource);

}

同样,这个扩展需要使用服务加载机制来配置。


类加载和字节码增强

这个过程是leap的启动的核心过程主要包含:

  • Model类字节码增强(提供数据库操作能力)
  • 事务注解的方法和类的字节码增强(提供事务控制能力)
  • 监控注解的字节码增强(提供监控能力)

可以实现自己的字节码增强处理器额外扩展增强能力:

package leap.core.instrument;

import leap.core.AppConfig;
import leap.lang.resource.Resource;

public interface AppInstrumentProcessor {

    default void init(AppConfig config) {

    }

    /**
     * Returns true if the processor changes the method body only.
     */
    default boolean isMethodBodyOnly() {
        return false;
    }

    /**
     * Returns true if the processor supports change the method body only.
     */
    default boolean supportsMethodBodyOnly() {
        return true;
    }

    /**
     * Returns true if the instrumented class should be redefined if define by class loader failed.
     */
    default boolean shouldRedefine() {
        return true;
    }

    /**
     * Returns true if the instrumented class should be redefined if define by same class loader.
     */
    default boolean shouldRedefineInSameClassLoader() {
        return false;
    }

    /**
     * Instrument the class.
     *
     * @param context the {@link AppInstrumentContext}.
     * @param resource the resource of the instrument class.
     * @param bytes the byte codes of the instrument class.
     * @param methodBodyOnly if true, the processor must use a mechanism which changes the method body only.
     */
    void instrument(AppInstrumentContext context, Resource resource, byte[] bytes, boolean methodBodyOnly);

}

不过没有必要的情况下非常不建议自己实现字节码增强处理器。

这个扩展需要使用服务加载机制配置。


加载所有Bean对象

在这个阶段开始,我们需要bean的时候已经不用再通过服务加载的方式配置了,可以直接使用IOC的依赖注入来获得bean对象了。

加载所有bean对象是一个复杂的过程,分为如下的环节:

  • 初始化操作
  • 创建所有需要的bean
    • 创建所有leap.core.ioc.BeanInjector
    • 创建所有leap.core.ioc.BeanProcessor
    • 创建所有leap.core.BeanFactoryInitializable
    • 创建所有leap.core.ioc.FactoryBean
    • 执行所有leap.core.ioc.BeanProcessor的创建后处理
    • 对所有定义好的bean执行leap.core.ioc.BeanProcessorpostInitBean方法
    • 执行所有leap.core.BeanFactoryInitializable的后初始化方法
    • 创建所有leap.core.config.dyna.PropertyProvider对象
    • 创建所有非懒加载的bean对象
  • bean工厂完成初始化

在leap中,所有的bean默认都是懒加载的,只有特定配置指明非懒加载的才会在启动的时候直接加载。

上面的过程是leap整体的bean创建顺序,在上面的过程中,没有创建的bean会在这个bean第一次被使用的时候创建,比如在代码中获取,或者将这个bean注入到另一个bean中,leap都会先创建这个bean,因此,leap对bean的创建,本身也有一个过程:

  • 创建bean对象
  • 检查bean对象所有依赖对象
  • 依次创建bean的依赖对象
  • 将bean的依赖对象注入到bean中
  • 返回创建的bean

这个过程是递归的,当bean的依赖对象也有依赖对象时,会再进一步先创建依赖对象的依赖对象,一直到最顶级无依赖的对象创建后逐层返回。

扩展IOC注入器

从上面的过程我们可以看出来,我们可以创建自己的依赖注入器来做一些扩展的依赖注入:

package leap.core.ioc;

import leap.lang.Out;
import leap.lang.reflect.ReflectValued;
import java.lang.annotation.Annotation;

public interface BeanInjector {
    default boolean resolveInjectValue(BeanDefinition bd, Object bean, ReflectValued v, Annotation a, Out<Object> value) {
        return false;
    }
}

要使用这个扩展,必须在xml中指定type,如:

<bean type="leap.core.ioc.BeanInjector" class="leap.orm.dao.DaoCommandInjector"/>

一般情况下我们是不建议做这个扩展的,因为leap本身的IOC依赖注入已经足够强大,正常的需求都不需要自己做这个扩展。

扩展bean处理器

在所有的bean配置加载完成之后,我们可以创建一个bean处理器来对所有未创建的bean对象进行定义修改:

package leap.core.ioc;

import leap.core.AppContext;
import leap.core.BeanFactory;

public interface BeanProcessor {

    default void postCreateProcessors(AppContext context, BeanFactory factory) throws Throwable {

    }

	default void postInitBean(AppContext context, BeanFactory factory, BeanDefinitionConfigurator c) throws Throwable {
        
    }
    
	default void postCreateBean(AppContext context, BeanFactory factory, BeanDefinition def, Object bean) throws Throwable {

    }

}

我们可以实现这个接口,然后在xml中配置我们的bean,leap即会在所有的bean定义加载完成之后,先创建这个处理器的bean对象,然后执行处理器的创建后处理:

postCreateProcessors(AppContext context, BeanFactory factory)

所有的处理器创建完成之后,就会可以开始用bean处理器对未创建的bean对象的定义进行修改或扩展了:

postInitBean(AppContext context, BeanFactory factory, BeanDefinitionConfigurator c)

注意: 处理器虽然能对bean定义进行修改,但是如果这个时候bean对象已经创建了,那么处理器修改了bean定义也是无效的,因此对于我们需要通过扩展的处理器进行bean定义修改的,必须保证bean不会在处理器之前创建,所以bean处理器中不要依赖我们会修改定义的bean。

最后,当我们的bean创建完成之后,处理器如果也需要做一些处理的话,会执行:

postCreateBean(AppContext context, BeanFactory factory, BeanDefinition def, Object bean)

这里是所有的bean创建的时候都会进行处理的扩展,后面我们还会看到,如果只有某个特定的bean需要后处理的话,预留了另外一个扩展。

要使用这个扩展,必须在xml中指定type,如:

<bean type="leap.core.ioc.BeanProcessor" class="leap.core.jmx.JmxBeanProcessor"/>

扩展bean工程初始化能力

在bean处理器完成对bean定义的处理之后,bean工厂会来说执行初始化扩展,我们可以在这个时候进行扩展,对整个bean工程的配置和bean定义做一些检查或者其他处理:

package leap.core;

import leap.core.ioc.BeanDefinitions;

public interface BeanFactoryInitializable {

    void postInit(AppConfig config, BeanFactory factory, BeanDefinitions definitions) throws Throwable;

}

实现这个接口即会在所有bean处理器处理定义之后,所有非懒加载的bean创建之前做一些处理。

要使用这个扩展,必须在xml中指定type,如:

<bean type="leap.core.BeanFactoryInitializable"          class="tested.init.TestingBeanFactoryInitializable"/>

创建Property提供器

这个阶段会创建所有的leap.core.config.dyna.PropertyProvider,这个提供器可以让我们在获取配置的property之前先做一些处理,比如某些需要动态获取的属性,无法写在配置文件中,就可以使用这个提供器来处理,这个扩展其实并不是启动时的扩展,但是这类bean的依赖对象会在这个时候被创建,因此还是需要提一下,当然,这个bean的创建需要在xml中指定type,如:

<bean type="leap.core.config.dyna.PropertyProvider" class="leap.core.event.DefaultEventRegisterImpl"/>

创建所有非懒加载的bean对象

这个阶段会创建所有指定了非懒加载的对象,这个过程的扩展就是在一个bean被创建过程的扩展了。

到这里整个bean工程的初始化就完成了。


bean的创建过程

在加载所有bean对象的过程,每个bean被加载之后都会有一个创建过程,因此,如果我们清楚某两个bean的创建先后顺序,也可以通过对这两个bean创建过程的扩展来实现启动过程的扩展,我们已经知道bean的创建过程:

  • 创建bean对象
  • 检查bean对象所有依赖对象
  • 依次创建bean的依赖对象
  • 将bean的依赖对象注入到bean中
  • 执行bean处理器的创建后处理
  • 执行bean本身的创建后处理
  • 执行bean的初始化
  • 执行bean的加载操作
  • 返回创建的bean

这个过程中,提供了bean创建的后处理扩展接口。

这里我们能扩展的是上述黑体的四个阶段,处理器的后处理阶段我们已经知道了,现在我们看一下后续三个阶段:

bean本身的创建后处理

leap提供了接口:

package leap.core.ioc;

import leap.core.BeanFactory;

public interface PostCreateBean {

	void postCreate(BeanFactory factory) throws Throwable; 
	
}

如果某个bean需要执行创建后处理的话,只要实现这个接口即可。

执行bean的初始化

leap提供了接口:

package leap.lang;

public interface Initializable {

    void init();

}

如果bean需要在后处理完成后执行初始化的话,可以用在这个方法执行,比如这个bean作为启动某个模块的启动类的时候,可以在init中完成对模块的启动。

执行bean的加载操作

bean的加载操作,主要是考虑到某些模块初始化完成之后,还需要做一些其他的操作,比如数据源,在bean对象创建成功之后,需要加载数据库驱动类的话,可以在这个阶段预先加载,不用等到使用的时候再加载,实现如下接口:

package leap.core.ioc;

import leap.core.BeanFactory;


public interface LoadableBean {

	boolean load(BeanFactory factory) throws Exception; 
	
}

到这里bean的创建就完成了。

在使用这个扩展的过程可以不在配置中指定type。

启动完成

bean工程初始化完成之后,leap会执行最后的启动后处理完成应用的启动,这个阶段leap提供了接口:

package leap.core;

public interface AppContextInitializable {
	
	void postInit(AppContext context) throws Throwable;

}

只要实现这个接口,leap就会在启动完成后执行postInit方法做后处理,当然,实现这个接口之后,需要在xml中配置bean对象,并且指定type是leap.core.AppContextInitializable才行,如:

<bean type="leap.core.AppContextInitializable" class="leap.core.event.DefaultEventRegister"/>

web启动

web的启动比leap单例启动稍复杂一些,具体的过程如下:

  • 启动web容器
  • 启动leap内核
  • 初始化web应用
  • 启动leap的web应用

这里web容器可以是tomcat一类的容器,然后由容器启动leap,leap启动完成之后,才真正开始启动web应用,因此我们主要看web应用的初始化和启动过程:

  • web应用配置预处理
  • web应用配置
  • web应用配置后处理
  • web应用初始化预处理
  • web应用初始化
  • web应用初始化扩展处理
  • web应用初始化后处理
  • web应用初始化完成
  • web应用启动预处理
  • web应用启动
  • web应用启动后处理
  • web应用停止预处理
  • web应用停止
  • web应用停止后处理
  • web应用销毁

整个启动到停止的过程,所有的预处理和后处理都是通过监听器实现的,leap提供了扩展接口:

package leap.web;

import leap.web.config.WebConfig;
import leap.web.config.WebConfigurator;

public interface AppListener {
	
	default void preAppConfigure(App app, WebConfigurator c) throws Throwable {
		
	}
	
	default void postAppConfigure(App app, WebConfig c) throws Throwable {
		
	}
	
	default void preAppInit(App app) throws Throwable {
		
	}
	
	default void postAppInit(App app) throws Throwable {
		
	}
	
	default void preAppStart(App app) throws Throwable {
		
	}
	
	default void postAppStart(App app) throws Throwable {
		
	}
	
	default void preAppStop(App app) throws Throwable {
		
	}
	
	default void postAppStop(App app) throws Throwable {
		
	}

}

只要实现这个接口,并在xml中配置这个bean即可。

另外,在初始化后,初始化后处理前,还提供了初始化扩展接口:

package leap.web;

public interface AppInitializable {

    void postAppInit(App app) throws Throwable;
    
}

只要实现这个接口,并在xml中定义这个bean即可,注意type必须是这个接口的子类才行。

web初始化全部完成之后,在启动之前,leap还预留了另一个接口:

package leap.web;

import javax.servlet.ServletContext;

public interface AppBootable {

    /**
     * Called on booting the web application.
     */
	void onAppBooting(App app, ServletContext sc) throws Exception;

    /**
     * Called after stopping the web application.
     */
    default void onAppStopped(App app, ServletContext sc) throws Exception {

    }
}

这个接口扩展比较少用,只在两个阶段生效,即初始化完成和应用最后销毁,需要扩展这个接口,可以实现这个接口并在xml中配置type为leap.web.AppBootable的子类的bean。

总结

leap的启动过程是一个非常繁琐和复杂的过程,在这个过程中提供了相当多的扩展接口,需要仔细理解每个阶段需要做的时候才能帮助我们理解整个启动过程的扩展。