首页 > 其他分享 >solon框架AopContext 接口详解

solon框架AopContext 接口详解

时间:2023-02-19 03:44:05浏览次数:41  
标签:solon name BeanWrap anno bean 详解 type void AopContext

AopContext接口

beanMake(Class<?> clz)

使用场景:

在开发插件(或在一些特殊条件下),自动扫描组件没有被扫描到,一般是因为要注册的组件没有在启动类的包下。

// 启动类所在包为 org.xxx
package org.xxx;

import org.noear.solon.Solon;
public class Main {
    public static void main(String[] args) {
        Solon.start(Main.class,args);
    }
}


// 组要注册的 controller 组件却在 com.test 包下,此时TestController 组件是不会被扫描到的
package com.test;

import org.noear.solon.annotation.Controller;
import org.noear.solon.annotation.Mapping;

@Controller
public class TestController {
    @Mapping("t1")
    public String t1(){
        return "t1";
    }
}

由此可以使用 context.beanMake(TestController.class);来注册组件,并且会触发@Controller 的注解功能

beanBuilderAdd(Class anno, BeanBuilder builder)

使用场景:给带指定注解的类赋能(自定义注解),比如@MyController,@MyService 注解等

anno 参数为 注解类,

builder参数为 一个函数式接口,可以传递lambda 表达式或者实现了 BeanBuilder 接口的实现类

BeanBuilder类中的参数列表

void doBuild(Class<?> clz, BeanWrap bw, T anno) throws Throwable;

源码示例:

/** AopContext.java
* ::初始化(独立出 initialize,方便重写)
*/
protected void initialize() {

    // ...
    
    //注册 @Controller 构建器
    beanBuilderAdd(Controller.class, (clz, bw, anno) -> {
        new HandlerLoader(bw).load(Solon.app());
    });

    //注册 @ServerEndpoint 构建器
    beanBuilderAdd(ServerEndpoint.class, (clz, wrap, anno) -> {
        if (Listener.class.isAssignableFrom(clz)) {
            Listener l = wrap.raw();
            Solon.app().router().add(Utils.annoAlias(anno.value(), anno.path()), anno.method(), l);
        }
    });


    //注册 @Inject 注入器
    beanInjectorAdd(Inject.class, ((fwT, anno) -> {
        beanInject(fwT, anno.value(), anno.required(), anno.autoRefreshed());
    }));
}

执行了beanInjectorAdd方法后,传入的lambda 表达式 或者 实现了BeanBuilder 的类中的内容不会立即执行,它们会全部添加到beanBuilders 的Map集合中

/** BeanContainer.java
* bean 构建器
*/
protected final Map<Class<?>, BeanBuilder<?>> beanBuilders = new HashMap<>();

//
public <T extends Annotation> void beanBuilderAdd(Class<T> anno, BeanBuilder<T> builder) {
    beanBuilders.put(anno, builder);
}

那它们执行的时机是什么?通过查看beanBuilders.get()方法,可以看到获取它的时机。

源码分析

// AopContext.java
// 片段1
public void beanScan(Class<?> source) {
    //确定文件夹名
    if (source.getPackage() != null) {
        beanScan(source.getClassLoader(), source.getPackage().getName()); //跟踪这里
    }
}
/**  AopContext.java
*	// 片段2
* ::扫描源下的所有 bean 及对应处理
*/
public void beanScan(ClassLoader classLoader,String basePackage) {
    if (Utils.isEmpty(basePackage)) {
        return;
    }

    if (classLoader == null) {
        return;
    }

    String dir = basePackage.replace('.', '/');

    //扫描类文件并处理(采用两段式加载,可以部分bean先处理;剩下的为第二段处理)
    ScanUtil.scan(classLoader, dir, n -> n.endsWith(".class"))
        .stream()
        .sorted(Comparator.comparing(s -> s.length()))
        .forEach(name -> {
            String className = name.substring(0, name.length() - 6);
            className = className.replace("/", ".");

            Class<?> clz = Utils.loadClass(classLoader, className);
            if (clz != null) {
                tryCreateBean(clz); //跟踪这里
            }
        });
}
/**
* // 片段3
* 尝试生成 bean
*/
    protected void tryCreateBean(Class<?> clz) { // 跟踪到这
        tryCreateBean0(clz, (c, a) -> {
            //包装 flag1
            BeanWrap bw = this.wrap(clz, null);
            c.doBuild(clz, bw, a);
            //尝试入库
            this.putWrap(clz, bw);
        });
    }

	// 片段4
    protected void tryCreateBean0(Class<?> clz, BiConsumerEx<BeanBuilder, Annotation> consumer) {
        Annotation[] annS = clz.getDeclaredAnnotations();

        if (annS.length > 0) {
            //去重处理
            if (tryCreateCached.contains(clz)) {
                return;
            } else {
                tryCreateCached.add(clz);
            }

            //增加条件检测
            if(ConditionUtil.test(this, clz) == false){
                return;
            }

            for (Annotation a : annS) {
                // 从beanBuilders集合中获取每个注解对应能力,即调用context.beanBuilderAdd(anno, builder)
                // 方法时传递的 builder 对象
                BeanBuilder builder = beanBuilders.get(a.annotationType());
                if (builder != null) {
                    try {
                        // 执行accept() 方法后才会真正执行builder对象中的 doBuild 方法
                        // 即 下一步会执行到[片段3] 中的 flag1 处
                        consumer.accept(builder, a);
                    } catch (Throwable e) {
                        e = Utils.throwableUnwrap(e);
                        if (e instanceof RuntimeException) {
                            throw (RuntimeException) e;
                        } else {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        }
    }

beanInjectorAdd(Class anno, BeanInjector injector)

使用场景:

自定义注解,让注解有@Inject{aa.xx} (如spring @Value{aa.bb})的能力

anno 参数为 注解类,

injector参数为 一个函数式接口,可以传递lambda 表达式或者实现了 BeanInjector 接口的实现类

BeanInjector类中的参数列表

void doInject(VarHolder varH, T anno);

使用方式案例

@Override
public void start(AopContext context) {
    // 以实现类的方式注册
    // 注册CloudConfig 注入器
    context.beanInjectorAdd(CloudConfig.class, CloudConfigBeanInjector.instance);

    //lambda 方式
    //注册 @Inject 注入器
    beanInjectorAdd(Inject.class, ((fwT, anno) -> {
        beanInject(fwT, anno.value(), anno.required(), anno.autoRefreshed());
    }));
}
protected void beanInject(VarHolder varH, String name, boolean required, boolean autoRefreshed) {
    try {
        beanInjectDo(varH, name, required, autoRefreshed);
    } catch (InjectionException e) {
        throw e;
    } catch (Throwable e) {
        throw new InjectionException("Injection failed: " + varH.getFullName(), e);
    }
}

CloudConfigBeanInjector.java 源码

public class CloudConfigBeanInjector implements BeanInjector<CloudConfig> {
    public static final CloudConfigBeanInjector instance = new CloudConfigBeanInjector();

    @Override
    public void doInject(VarHolder varH, CloudConfig anno) {
        if (CloudClient.config() == null) {
            throw new IllegalArgumentException("Missing CloudConfigService component");
        }

        //支持${xxx}配置
        String name = Solon.cfg().getByParse(Utils.annoAlias(anno.value(), anno.name()));
        //支持${xxx}配置
        String group = Solon.cfg().getByParse(anno.group());

        Object tmp1 = build(varH.getType(), group, name);
        if (tmp1 != null) {
            varH.setValue(tmp1);
        }

        if (varH.isField() && anno.autoRefreshed()) {
            CloudClient.config().attention(group, name, (cfg) -> {
                Object tmp2 = build0(varH.getType(), cfg);
                if (tmp2 != null) {
                    varH.setValue(tmp2);
                }
            });
        }
    }

    public Object build(Class<?> type, String group, String name) {
        Config cfg = CloudClient.config().pull(group, name);

        return build0(type, cfg);
    }


    private Object build0(Class<?> type, Config cfg) {
        if (cfg == null || cfg.value() == null) {
            return null;
        }

        if (Properties.class.isAssignableFrom(type)) {
            return cfg.toProps();
        }

        if (type.getName().startsWith("java.lang.") || type.isPrimitive()) {
            //如果是java基础类型,则为null(后面统一地 isPrimitive 做处理)
            //
            return ConvertUtil.to(type, cfg.value());
        } else {
            //尝试转为实体
            //
            Properties val0 = cfg.toProps();
            return PropsConverter.global().convert(val0, null, type, null);
        }
    }
}

beanExtractorAdd(Class anno, BeanExtractor extractor)

使用场景:

自定义注解,让注解具有解析BeanWrap、method和anno字段信息的功能,此接口注重的功能是提取信息

anno 参数为 注解类,

injector参数为 一个函数式接口,可以传递lambda 表达式或者实现了 BeanInjector 接口的实现类

BeanExtractor 方法内的参数列表

void doExtract(BeanWrap bw, Method method, T anno)

beanAroundAdd(Class anno, Interceptor interceptor, int index)

使用场景:

自定义注解,让注解具有拦截某些方法的功能,例如缓存、事务控制等

anno 参数为 注解类,

interceptor参数为 一个函数式接口,可以传递lambda 表达式或者实现了 Interceptor 接口的实现类

index 参数为 执行顺序的索引,

Interceptor 方法内的参数列表

    /**
     * 拦截
     *
     * @param inv 调用者
     * */
    Object doIntercept(Invocation inv) throws Throwable;

InterceptorEntity beanAroundGet(Class anno)

使用场景:

获取给定 具有拦截器属性注解 的拦截器

anno 参数为 拦截器注解类

beanScan(Class<?> source)

beanScan(String basePackage)

beanScan(ClassLoader classLoader,String basePackage)

扫描源下的所有 bean 及对应处理,以上三个功能相同

beanInject(Object obj)

使用场景:

注入Bean对象(即生成对应类型的Bean)

obj 参数为 要生成Bean的对象

beanInject(VarHolder varH, String name)

使用场景:

注入字段属性值,

varH 变量包装器

name bean name || config $

beanInject(VarHolder varH, String name, boolean required, boolean autoRefreshed)

使用场景:

给指定的变量(varH),根据name的表达式设置值,在类中字段标注了@Inject的字段会调用此方法,如果name为空则是字段属性注入,不为空则是给字段设置

${xxx} 表达式所设置的值。

varH 参数为 变量包装器

name bean name || config $

required 是否必须

autoRefreshed 是否自动刷新

//注册 @Inject 注入器
beanInjectorAdd(Inject.class, ((fwT, anno) -> {
    beanInject(fwT, anno.value(), anno.required(), anno.autoRefreshed());
}));

beanOnloaded(Consumer fun)

beanOnloaded(int index, Consumer fun)

使用场景:

添加bean加载完成事件,在所有的Bean被加载完成后对指定的类做一些操作,例如初始化工作、判断该类型是否添加到了容器中,如果没有可以在此处添加到容器中

fun 参数为 一个消费型接口

index 执行顺序的索引

源码示例:

//添加缓存控制支持
context.beanOnloaded((ctx) -> {
    if (ctx.hasWrap(CacheService.class) == false) {
        ctx.wrapAndPut(CacheService.class, LocalCacheService.instance);
    }
});

// smarthttp 服务器启动时 执行solon应用启动
@Override
public void start(AopContext context) {
    if (Solon.app().enableHttp() == false) {
        return;
    }

    context.beanOnloaded((ctx) -> {
        try {
            start0(Solon.app());
        } catch (RuntimeException e) {
            throw e;
        } catch (Throwable e) {
            throw new IllegalStateException(e);
        }
    });
}

运行与加载时机

// 添加完的事件全部缓存到了loadEvents中

//加载事件
private final Set<RankEntity<Consumer<AopContext>>> loadEvents = new LinkedHashSet<>();

@Note("添加bean加载完成事件")
public void beanOnloaded(int index, Consumer<AopContext> fun) {
    loadEvents.add(new RankEntity<>(fun, index));

    // 如果已加载完成,则直接返回
    if (loadDone) {
        fun.accept(this);
    }
}

当服务启动完成后会调用此方法将 loadDone 赋值为 true

/**
 * 完成加载时调用,会进行事件通知
 */
public void beanLoaded() {
    loadDone = true;

    //执行加载事件(不用函数包装,是为了减少代码)
    loadEvents.stream()
            .sorted(Comparator.comparingInt(m -> m.index))
            .forEach(m -> m.target.accept(this));
}

用例

public abstract class Gateway extends HandlerAide implements Handler, Render {
    // 省略字段和构造函数
    
        /**
     * 注册相关接口与拦截器
     */
    @Note("注册相关接口与拦截器")
    protected abstract void register();
    
        @Note("添加接口")
    public void addBeans(Predicate<BeanWrap> where) {
        addBeans(where, false);
    }

    /**
     * remoting 的 bean 建议一个个添加,并同时添加前缀 path
     */
    @Note("添加接口")
    public void addBeans(Predicate<BeanWrap> where, boolean remoting) {
        
        Solon.context().beanOnloaded((ctx) -> {
            ctx.beanForeach(bw -> {
                if (where.test(bw)) {
                    if (remoting) {
                        add(bw, remoting); //强制为 remoting
                    } else {
                        add(bw); //自动判定
                    }
                }
            });
        });
    }
}
@Mapping("/t2/**")
@Component
public class TestGatewat extends Gateway {
    @Override
    protected void register() {
        //添加服务(不带mapping的函数;需要 remoting = true,才会加载出来)
        add("user", UserServiceImpl.class, true);
        // 重点看这里,此方法会触发抽象类中的 beanOnloaded 方法
        addBeans(bw -> "api".equals(bw.tag()));
    }
}

解析:

solon框架中自带的Gateway抽象类中有一个register() 方法,此方法是必须要重写的,在此可以调用父类中的addBeans 方法,从而触发beanOnloaded 事件的执行。

beanForeach(BiConsumer<String, BeanWrap> action)

action 是一个消费型接口,支持传递两个参数的BiConsumer接口的实现类和lambda表达式

beanForeach(Consumer action)

action 是一个消费型接口,支持传递一个参数的Consumer接口的实现类和lambda表达式

使用场景:

一般结合 subWrapsOfType(Class<?> baseType, Consumer<BeanWrap> callback)beanOnloaded(Consumer<AopContext> fun) 进行使用。因为这两个方法执行的时机已经是将bean 扫描之后。

遍历bean包装库。

public void subWrapsOfType(Class<?> baseType, Consumer<BeanWrap> callback) {
    //获取现有的
    beanForeach(bw -> {
        if (baseType.isAssignableFrom(bw.clz())) {
            callback.accept(bw);
        }
    });

    //获取未来的
    wrapExternalSubscribe((bw) -> {
        if (baseType.isAssignableFrom(bw.clz())) {
            callback.accept(bw);
        }
    });
}
context.beanOnloaded((ctx) -> {
    ctx.beanForeach((k, v) -> {

        System.out.println(k);
        System.out.println(v);
    });
});

List beanFind(Predicate condition)

condition 是一个断言型接口,支持传递一个参数的Predicate接口的实现类,和lambda 表达式

List beanFind(BiPredicate<String, BeanWrap> condition)

condition 是一个断言型接口,支持传递两个参数的Predicate接口的实现类,和lambda 表达式

使用场景:

查找bean包装库。这两个方法也是对执行时机有要求,参考beanForeach方法的执行时机。

示例:

context.beanOnloaded((ctx) -> {
    ctx.beanFind(beanWrap -> {
        System.out.println("------------------");
        System.out.println(beanWrap);
        System.out.println("------------------");
        return false;
    });
});

void beanRegister(BeanWrap bw, String name, boolean typed)

使用场景:

注册BeanWrap 到容器中(按名字和类型存入容器;并进行类型印射)

bw 对象的包装类

name 要注册Bean的name

typed 是否要按类型注册

示例

UserUtil userUtil = new UserUtil(type.getUserType());  			// 创建对象
BeanWrap userUtilWrapByName = context.wrap(type, userUtil);  	// 将对象包装成BeanWarp
context.beanRegister(userServiceWrapByName,"beanName",true);	// 注册BeanWrap 到容器中

BeanWrap wrap(Class<?> type, Object bean)

使用场景:

将指定对象按照给定类型包装成BeanWrap

type 对象类型

bean 要包装的对象

context.wrap(Dispatcher.class, dispatcher);

BeanWrap wrap(String name, Object bean)

使用场景:

将指定对象按照给定name包装成BeanWrap

name beanName

bean 要包装的对象

context.wrap(Dispatcher.class, "beanName");

BeanWrap wrapAndPut(Class<?> type)

使用场景:

包装并推入容器

type 对象类型

context.wrapAndPut(Dispatcher.class);

BeanWrap wrapAndPut(Class<?> type, Object bean)

使用场景:

包装并推入容器

type 对象类型

bean 对象实例

context.wrapAndPut(Dispatcher.class,dispatcher);

void putWrap(String name, BeanWrap wrap)

使用场景:

将BeanWrap按照name 推送到容器,如果wrap为null,拒绝注册

name beanName

wrap 对象包装实例

void putWrap(Class<?> type, BeanWrap wrap)

使用场景:

将BeanWrap按照type推送到容器,如果type 和 wrap为null,拒绝注册

type 对象类型

wrap 对象包装实例

boolean hasWrap(Object nameOrType)

使用场景:

根据BeanWrap的Name 或 Bean的类型 判断 该"bean"是否有包装类型,本质上就是从Map中根据Key取值,源码如下getWrap

nameOrType bean类型或者BeanWrap的name(即包装成BeanWrap时所传入的name)

// BeanContainer.java
private final Map<String, BeanWrap> beanWrapsOfName = new HashMap<>();
private final Map<Class<?>, BeanWrap> beanWrapsOfType = new HashMap<>();

public boolean hasWrap(Object nameOrType) {
    return getWrap(nameOrType) != null;
}

BeanWrap getWrap(Object nameOrType)

使用场景:

根据BeanWrap的Name 获取包装类型,本质上就是从Map中根据Key取值。

nameOrType bean类型或者BeanWrap的name(即包装成BeanWrap时所传入的name)

/**
* 获取一个bean包装
*
* @param nameOrType bean name or type
*/
public BeanWrap getWrap(Object nameOrType) {
    if (nameOrType instanceof String) {
        return beanWrapsOfName.get(nameOrType);
    } else {
        return beanWrapsOfType.get(nameOrType);
    }
}

void getWrapAsync(Object nameOrType, Consumer callback)

使用场景:

异步方式获取BeanWrap,一般在插件开发中用于获取BeanWrap

nameOrType bean类型或者BeanWrap的name(即包装成BeanWrap时所传入的name)

callback 回调函数,一个消费型接口

@Override
public void start(AopContext context) throws Throwable {
    Props cfg = context.cfg();

    context.getWrapAsync(Dispatcher.class, bw -> {
        // 此处能获取到
        System.out.println(bw);

    });
    // 这里 wrap 为 null
    BeanWrap wrap = context.getWrap(Dispatcher.class);

}
// 前提是对象 一定是要注册到容器中的!

void subWrapsOfType(Class<?> baseType, Consumer callback)

使用场景:

订阅某类型的 bean 包装bean

baseType 基类(抽象类或者接口、普通类)一般是接口

callback 消费型接口,回调函数

示例:

@Component
public class TestGatewat extends Gateway {

}
public abstract class Gateway extends HandlerAide implements Handler, Render {
    //....
}
public class XPluginImp implements Plugin {

    @Override
    public void start(AopContext context) throws Throwable {
		// 由于 TestGatewat 继承于Gateway,Gateway 又实现了Handler接口,而TestGatewat 又被@Component注解所标注
        // 所以以下输出的beanWrap 就是TestGatewat的内容
        context.subWrapsOfType(Handler.class, beanWrap -> {
            System.out.println(beanWrap);
        });

    }

}

注意:subBeansOfTypesubWrapsOfType,仅对 @Bean@Component 产生的 Bean 有效

T getBean(String name)

使用场景:

根据bean名字 获取bean

name bean名字

T getBean(Class type)

使用场景:

根据bean type获取bean

type bean类型

List getBeansOfType(Class baseType)

使用场景:

根据baseType(基类)获取bean的集合

baseType 基类

T getBeanOrNew(Class type)

使用场景:

根据type 获取bean,如果不存在则创建

type 类型

void getBeanAsync(String name, Consumer callback)

使用场景:

根据name异步获取bean,使用方式和getWrapAsync 理念基本一致

name beanName

callback 回调函数

void getBeanAsync(Class type, Consumer callback)

使用场景:

根据type异步获取bean,使用方式和getWrapAsync 理念基本一致

type 类型

callback 回调函数

void subBeansOfType(Class baseType, Consumer callback)

使用场景:

订阅某类型的 bean ,使用方式与 subWrapsOfType 基本一致

baseType 基类(抽象类或者接口、普通类)一般是接口

callback 消费型接口,回调函数

部分接口限制说明

  • subBeansOfType、subWrapsOfType,仅对 @Bean 或 @Component 产生的 Bean 有效
  • beanExtractorAdd 仅对 @Component 产生的 Bean 有效
  • beanAroundAdd 仅对被代理的 Bean 有效,比如:@Controller、@Service、@Dao 等

标签:solon,name,BeanWrap,anno,bean,详解,type,void,AopContext
From: https://www.cnblogs.com/lionelk/p/17134151.html

相关文章

  • 从零入门Vue.js!六步学习路线和知识体系盘点详解!
    Vue.js是一款流行的JavaScript前端框架,它允许开发者轻松地构建交互性强的用户界面。学习这个阶段的时候有一定门槛,并不是属于零基础就能入门学习的,在学习vue.js的时候可以......
  • 【云原生kubernetes】k8s service使用详解
    一、什么是服务service?在k8s里面,每个Pod都会被分配一个单独的IP地址,但这个IP地址会随着Pod的销毁而消失,重启pod的ip地址会发生变化,此时客户如果访问原先的ip地址则会报错;S......
  • 伸展树(Splay)详解
    引入在一条链中,二叉查找树的时间复杂度就会退化成\(O(n)\),这时我们就需要平衡树来解决这个问题。\(Splay\)(伸展树)是平衡树的一种,它的每一步插入、查找和删除的平摊时间......
  • [GNU C] __attribute___(section("name")) 详解 (转载)
    本文详细讲解了利用__attribute__((section()))构建初始化函数表,以及Linux内核各级初始化的原理......
  • 从入门到进阶:Elasticsearch高级查询技巧详解
    Elasticsearch是一款功能强大的全文搜索引擎,它使用Lucene搜索库进行底层索引和搜索。Elasticsearch提供了许多高级查询技巧,可以帮助用户更准确、更高效地查询数据。本教程将......
  • k8s 证书详解
    在进行二进制搭建K8S集群前,我们需要梳理最磨人的一个点,就是各种各样的证书。官方文档参考:https://kubernetes.io/docs/setup/certificates/一共有多少证书:先从Etcd算起:......
  • STM32 - 定时器1 - 定时器详解
    目录1.什么是定时器1.1数量&来源1.2用途2.通用定时器框图2.1输入时钟2.2时基单元2.3输入捕获/输出比较通道1)输入阶段:2)输出阶段:3.模式配置3.1计数器模式3.2输入捕......
  • linux源码解析13- 反向映射RAMP详解
    1.什么是反向映射是一种物理地址反向映射虚拟地址的方法;正向映射:用户访问的虚拟地址,经过多级页表转化,最终映射到物理页面;反向映射:根据物理页面,找到所有映射到这个页面的......
  • 常用的24/22/16/8位网段详解
    24位网段10.8.0.0/24 10.8.0.0---10.8.0.254 0-255 1*256=256个ip10.8.20.0/24 10.8.20.0---10.8.0.254 0-255 1*256=256个ip10.8.100.0/24 10.8.100.0......
  • 【Bluetooth开发】一、蓝牙开发入门详解
     ​编辑BLE蓝牙设备在生活中无处不在,但是我们也只是将其作为蓝牙模块进行使用,发送简单的AT命令实现数据收发。那么,像对于一些复杂的使用场合:“车载蓝牙”、"智能手表"、“......