Skip to content

Latest commit

 

History

History
218 lines (175 loc) · 10.5 KB

如何适配Summer.md

File metadata and controls

218 lines (175 loc) · 10.5 KB

如何适配Summer

​ Summer是一款为了应用程序各个部件之间解耦合而诞生的Java框架,通过IOC的容器化管理对象,很方便的解决了各个对象之间依赖问题,同时通过AOP可以在外部无侵入式地将一些日志记录,权限控制等等和核心业务无关的操作插入代码中。

​ 为了打造Summer的生态,所以需要Summer能被其他应用场景的框架适配,从而达到更好的扩展性。所以在的更新中,Summer对外开放了扩展接口Extension,通过有选择性地实现此接口中的八个方法,可以在IOC容器构造的各个阶段插入需要扩展的代码,从而使Summer可以和其他应用框架适配起来。

Summer对外提供的Extension接口如下定义:

/**
 * @PackageName: com.vfd.summer.extension
 * @ClassName: Extension
 * @Description: 为此框架的扩展向外提供一个接口
 * @author: vfdxvffd
 * @date: 2021/5/12 下午1:28
 */
public interface Extension {

    void doOperation0 (SummerAnnotationConfigApplicationContext context) throws Exception;

    void doOperation1 (SummerAnnotationConfigApplicationContext context) throws Exception;

    void doOperation2 (SummerAnnotationConfigApplicationContext context) throws Exception;

    void doOperation3 (SummerAnnotationConfigApplicationContext context) throws Exception;

    void doOperation4 (SummerAnnotationConfigApplicationContext context) throws Exception;

    void doOperation5 (SummerAnnotationConfigApplicationContext context, BeanDefinition beanDefinition) throws Exception;

    void doOperation6 (SummerAnnotationConfigApplicationContext context, Object o) throws Exception;

    void doOperation7 (SummerAnnotationConfigApplicationContext context, Object o) throws Exception;

    void doOperation8 (SummerAnnotationConfigApplicationContext context, Object o) throws Exception;
}

接口的方法会在ioc容器构造的各个阶段进行织入,织入的时机如下:

/**
     * 带有扩展性的构建ioc容器
     * @param propertyFile 类路径下的配置文件
     * @param extensions 外部扩展对象的列表
     * @param basePackages 需要被ioc管理的包
     */
public SummerAnnotationConfigApplicationContext(String propertyFile, List<? extends Extension> extensions, String... basePackages) throws Exception {
    this.extensions = extensions;
    this.propertyFile = propertyFile;
    for (Extension extension : this.extensions) {
        extension.doOperation0(this);
    }
    //遍历包,找到目标类(原材料)
    findBeanDefinitions(basePackages);
    for (Extension extension : this.extensions) {
        extension.doOperation1(this);
    }
    //根据原材料创建bean
    createObject();
    for (Extension extension : this.extensions) {
        extension.doOperation2(this);
    }
    //先将需要代理的对象进行动态代理
    proxyObject();
    for (Extension extension : this.extensions) {
        extension.doOperation3(this);
    }
    //自动装载并将切面类中的方法横切目标方法并装入ioc容器中
    autowireObject();
    for (Extension extension : this.extensions) {
        extension.doOperation4(this);
    }
    //容器初始化日志
    logger.info("IOC容器初始化完成");
}

/**
     * 对于非单例或者延迟加载的bean在此创建实例化、代理、初始化
     * @param beanDefinition
     * @return
     */
private Object createBean(BeanDefinition beanDefinition) throws Exception {
    for (Extension extension : extensions) {
        extension.doOperation5(this, beanDefinition);
    }
    //实例化一个对象,但并未初始化
    final Object object = createObject(beanDefinition);
    for (Extension extension : extensions) {
        extension.doOperation6(this, object);
    }
    //对刚刚实例化的对象进行代理处理,需要先判断是否需要代理
    final Object o = proxyObject(object);
    for (Extension extension : extensions) {
        extension.doOperation7(this, object);
    }
    //对代理后的对象(如果需要)进行注入工作
    autowireObject(object);
    for (Extension extension : extensions) {
        extension.doOperation8(this, object);
    }
    return o;
}

​ 在ioc初始化之前,扫描包结束之后,bean的实例化结束之后,bean的代理设置完成之后,bean的初始化完成之后这五个阶段,以及对于原型模式或者懒加载的bean,在对象实例化之前,对象设置代理之前,对象初始化之前以及最终构造完成的时候这四个阶段。

​ 实现扩展接口的类的方法可以获得SummerAnnotationConfigApplicationContext对象,通过插入自己的代码,修改其中的多级缓存中的内容、ioc容器、beanDefenation等等内容,即可实现对Summer的扩展。

示例

​ 这里我们通过一个例子来说明具体如何适配。以我的另一个项目vrpc为例。

vrpc是一款rpc框架,底层采用netty进行网络通信,通过动态代理生成接口的代理对象,接口的具体实现类可能在远程某个主机上,在调用时调用代理类的方法,向远程主机发送请求信息,在远程执行对应对象的方法后,将执行结果再通过netty发送响应消息,最终得到远程执行的结果。

vrpc项目的具体内容可以查看仓库:https://github.com/vfdxvffd/vrpc

适配vrpc到Summer

Summervrpc的适配后的仓库在:https://github.com/vfdxvffd/summer-vrpc,这里只做简要的说明。

容器构造过程中代码的织入

vrpc主要是对于需要远程调用的对象通过@Reference注解,通过指定远程主机的ip地址和端口号,以及消息传输过程中的序列化方式(可选)来创建代理对象注入到IOC容器中。既然是对对象域的操作,那就要等到对象实例化之后,对象初始化之前进行,我们选取了doOperation3这个时机进行对对象中引用远程对象的域的初始化工作。

// 对二级缓存中标注了@Reference注解的域进行注入
@Override
public void doOperation3 (SummerAnnotationConfigApplicationContext context) throws Exception {
    for (Map.Entry<String, Object> objectEntry : context.getEarlyRealObjects().entrySet()) {
        referenceObject(objectEntry.getValue());
    }
    logger.info("远程调用的代理对象设置完成");
}

// 这个函数就是对具体某个对象进行检查以及对其标注了@Reference的域进行初始化,具体实现可以忽略
private void referenceObject (Object object) throws Exception {
    final Class<?> clazz = object.getClass();
    for (Field field : clazz.getDeclaredFields()) {
        final Reference reference = field.getAnnotation(Reference.class);
        if (reference != null) {
            field.setAccessible(true);
            if (field.get(object) != null) {
                continue;
            }
            String host = "".equals(reference.host())? Config.getDestHost():reference.host();
            int port = reference.port() == -1? Config.getDestPort():reference.port();
            Serializer serializer = reference.serializer()==Serializer.class? Config.getDestSerializer():reference.serializer().newInstance();
            final Object remoteObj = getRemoteObj(host, port, serializer, reference.beanName(), field.getType());
            field.set(object, remoteObj);
        }
    }
}

通过接口获取到context对象,通过context对象拿到二级缓存保存实际对象的容器,遍历这个容器,检查如果有对象的域标注了@Reference注解,就对其进行代理对象的设置,并赋值回这个对象的域。

原型模式和懒加载模式的对象

对于原型模式或者懒加载模式的对象,由于不会在容器构造的时候就进行创建,所以需要在createBean方法中进行织入,同样选择对象实例化结束以及初始化之前的时机doOperation7

// 对此对象进行检查,如果其中的域包含了@Reference注解,则对其进行注入
@Override
public void doOperation7 (SummerAnnotationConfigApplicationContext context, Object o) throws Exception {
    referenceObject(o);
}

如何向IOC容器中插入操作的句柄

由于用户可能需要有选择性的控制连接的开启和关闭,所以需要为用户的操作提供一个句柄,例如spring中对于jdbcJdbcTemplate,我们这里的操作句柄为VRpcHandler,这个需求其实很简单,只是向IOC容器中加入一个对象,并将此beanTypebeanName对应起来即可,通过获取context的ioc容器即可操作,这个操作的时机可以选择在IOC容器构建之前,也可以选择在构建之后,都是比较合适的时机。这里我们选择doOperation2这个时机。

// 把控制、操作、修改rpc配置的句柄装配到ioc容器中
@Override
public void doOperation2 (SummerAnnotationConfigApplicationContext context) {
    context.getIocByName().put("vRpcHandler", new VRpcHandler(this));
    final HashSet<String> set = new HashSet<>();
    set.add("vRpcHandler");
    context.getBeanTypeAndName().put(VRpcHandler.class, set);
}

rpc在远程调用的时候如何向外提供服务

既然要向外提供服务,那么就应该等本地IOC容器构建完成之后,这时对象都交由IOC容器来管理,且已经全部构建完毕,我们可以选择doOperation4这个时机。

// 开启服务端监听端口,对外提供服务
@Override
public void doOperation4 (SummerAnnotationConfigApplicationContext context) throws Exception {
    if (!Config.providerServer()) {
        return;
    }
    int port = Config.getServerPort();
    final Serializer serializer = Config.getServerSerializer();
    provide0(port, serializer);
}

至此就已经完成了vrpcSummer的适配,构建Summer时只需要传入vrpc的对象实例即可,参考如下的构建示例:

public class APP {
    public static void main(String[] args) throws Exception {
        ApplicationContext ioc = new SummerAnnotationConfigApplicationContext(
            "/application.properties" , Collections.singletonList(VRpcAdapter.class.newInstance()), "com.vfd.service"
        );
        final VRpcHandler handler = ioc.getBean(VRpcHandler.class);
        handler.startProvideServer(9090);
    }
}

最后,如果您对Summer感兴趣,欢迎将您自己的框架适配到Summer,或者让我和您一起构建!