jboot02:controller & model &sharding jdbc

Controller部分

jboot使用了jfinal的mvc模块。

JbootController

自定义的Controller扩展了Controller的一些f方法。

  • isMoblieBrowser() //是否是手机浏览器
  • isWechatBrowser() //是否是微信浏览器
  • isIEBrowser() //是否是IE浏览器,低级的IE浏览器在ajax请求的时候,返回json要做特殊处理
  • isAjaxRequest() //是否是ajax请求
  • isMultipartRequest() //是否是带有文件上传功能的请求
  • getReferer() // 获取来源网址
  • getIPAddress() //获取用户的IP地址
  • getUserAgent() //获取http头的useragent
  • getBaseUrl() //获取当前域名
  • getUploadFilesMap() // 获取当前上传的所有文件
@RquestMapping注解

jfinal 需要手动子在配置类里面实现Route到Controller的映射。jboot提供了@RquestMapping注解。
关于action,render的具体方法可以参照我的jfinal都源码系列。本系列主要关注jboot对jfinal 的扩展以及对微服务的支持。
看下RquestMapping注解定义

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface RequestMapping {

    String value(); // 必填字段
  
    String viewPath() default "";//可选字段,默认为空。指向视图渲染的基础目录
}

再来看看RquestMapping的扫描。JbootAppConfig类的configRoute方法
JbootAppConfig作为jfinal的配置类,在jfinal初始化时会调用这个类的相关方法。

@Override
    public void configRoute(Routes routes) {

// 扫描Controller类的子类(稍后重点关注一下这个类的实现)
        List<Class<Controller>> controllerClassList = ClassScanner.scanSubClass(Controller.class);
        if (controllerClassList == null) {
            return;
        }

        for (Class<Controller> clazz : controllerClassList) {
// 获取类上的RequestMapping注解
            RequestMapping mapping = clazz.getAnnotation(RequestMapping.class);
            if (mapping == null || mapping.value() == null) {
                continue;
            }
    // 添加路由,此时也就完成了路由到Controller的映射
            if (StrKit.notBlank(mapping.viewPath())) {
                routes.add(mapping.value(), clazz, mapping.viewPath());
            } else {
                routes.add(mapping.value(), clazz);
            }
        }

// swagger的实现我们后面在看
        JbootSwaggerConfig swaggerConfig = Jboot.config(JbootSwaggerConfig.class);
        if (swaggerConfig.isConfigOk()) {
            routes.add(swaggerConfig.getPath(), JbootSwaggerController.class, swaggerConfig.getPath());
        }

        JbootAppListenerManager.me().onJfinalRouteConfig(routes);

        for (Routes.Route route : routes.getRouteItemList()) {
            JbootControllerManager.me().setMapping(route.getControllerKey(), route.getControllerClass());
        }

        routeList.addAll(routes.getRouteItemList());
    }
类扫描器ClassScanner
方法scanClass

扫描整个项目所有的类,boolean可选参数可以排除接口和抽象类

方法scanClassByAnnotation

扫描整个项目类,根据注解来筛选

方法scanClassFile

扫描指定path下的.class文件

方法scanSubClass

用于扫描指定父类的子类,实现的原理就是加载整个项目的Java类,循环遍历。采用JDK方法:class1.isAssignableFrom(parent)方法判断是否为子类

扫描原理
  • 扫描ContextPath路径下编译好的class文件


    image.png
  • 扫描jar文件里面的类(URLClassLoader)
    ClassScanner 类的 findJars方法
    URL[] urLs = urlClassLoader.getURLs();
    这里涉及到JVM的类加载原理,先找到java_home,再依次加载所有依赖的类。后期也需要深入学习一下。

model部分

@Table注解

jfinal 需要手动子在配置类里面实现表名到Model的映射。jboot提供了@Table注解。
来看一下从数据源到model映射的流程
1.JbootAppConfig类中初始化JbootDbManager类对象,直接获取类成员变量集合(activeRecordPlugins)
List<ActiveRecordPlugin> arps = JbootDbManager.me().getActiveRecordPlugins();
2.来看下JbootDbManager的初始化流程。这个初始化就完成了activeRecordPlugins集合的构建

public JbootDbManager() {

        // 【1】读取所有的数据源,包含了分库数据源的子数据源,涉及到读取配置文件   
        Map<String, DataSourceConfig> allDatasourceConfigs = DataSourceConfigManager.me().getDatasourceConfigs();

        // 分库的数据源,一个数据源包含了多个数据源。
        Map<String, DataSourceConfig> shardingDatasourceConfigs = DataSourceConfigManager.me().getShardingDatasourceConfigs();

        if (shardingDatasourceConfigs != null && shardingDatasourceConfigs.size() > 0) {
            for (Map.Entry<String, DataSourceConfig> entry : shardingDatasourceConfigs.entrySet()) {

                //子数据源的配置
                String shardingDatabase = entry.getValue().getShardingDatabase();
                if (StringUtils.isBlank(shardingDatabase)) {
                    continue;
                }
                Set<String> databases = StringUtils.splitToSet(shardingDatabase, ",");
                for (String database : databases) {
                    DataSourceConfig datasourceConfig = allDatasourceConfigs.remove(database);
                    if (datasourceConfig == null) {
                        throw new NullPointerException("has no datasource config named " + database + ",plase check your sharding database config");
                    }
                    entry.getValue().addChildDatasourceConfig(datasourceConfig);
                }
            }
        }

        //合并后的数据源,包含了分库分表的数据源和正常数据源
        Map<String, DataSourceConfig> mergeDatasourceConfigs = new HashMap<>();
        if (allDatasourceConfigs != null) {
            mergeDatasourceConfigs.putAll(allDatasourceConfigs);
        }

        if (shardingDatasourceConfigs != null) {
            mergeDatasourceConfigs.putAll(shardingDatasourceConfigs);
        }


        for (Map.Entry<String, DataSourceConfig> entry : mergeDatasourceConfigs.entrySet()) {

            DataSourceConfig datasourceConfig = entry.getValue();

            if (datasourceConfig.isConfigOk()) {

            // 【2】创建activeRecordPlugin插件,根据@Table注解添加
                ActiveRecordPlugin activeRecordPlugin = createRecordPlugin(datasourceConfig);
                activeRecordPlugin.setShowSql(Jboot.me().isDevMode());
                activeRecordPlugin.setCache(Jboot.me().getCache());

                configSqlTemplate(datasourceConfig, activeRecordPlugin);
                configDialect(activeRecordPlugin, datasourceConfig);

                activeRecordPlugins.add(activeRecordPlugin);
            }
        }

    }

【1】读取配置文件,构造DataSourceConfig 集合

private DataSourceConfigManager() {
// 读取jboot.properties配置文件中,以jboot.datasource前缀一致(即数据配置)的列表项     
【TODO】暂时没太看明白这里写的作用
        DataSourceConfig datasourceConfig = Jboot.config(DataSourceConfig.class, "jboot.datasource");

        //若未配置数据源的名称,设置为默认
        if (StringUtils.isBlank(datasourceConfig.getName())) {
            datasourceConfig.setName(DataSourceConfig.NAME_DEFAULT);
        }

        if (datasourceConfig.isConfigOk()) {
            datasourceConfigs.put(datasourceConfig.getName(), datasourceConfig);
        }
        
        if (datasourceConfig.isShardingEnable()) {
            shardingDatasourceConfigs.put(datasourceConfig.getName(), datasourceConfig);
        }


// 对多数据源分组  第三段参数作为分组标识  支持多数据源
        Properties prop = JbootConfigManager.me().getProperties();
        Set<String> datasourceNames = new HashSet<>();
        for (Map.Entry<Object, Object> entry : prop.entrySet()) {
            String key = entry.getKey().toString();
            if (key.startsWith(DATASOURCE_PREFIX) && entry.getValue() != null) {
                String[] keySplits = key.split("\\.");
                if (keySplits.length == 4) {
                    datasourceNames.add(keySplits[2]);
                }
            }
        }


        for (String name : datasourceNames) {
//遍历组名,分别获取每个数据源 配置对象  放到map集合 至此读取配置文件结束
            DataSourceConfig dsc = Jboot.config(DataSourceConfig.class, DATASOURCE_PREFIX + name);
            if (StringUtils.isBlank(dsc.getName())) {
                dsc.setName(name);
            }
            if (dsc.isConfigOk()) {
                datasourceConfigs.put(name, dsc);
            }
            if (dsc.isShardingEnable()) {
                shardingDatasourceConfigs.put(name, dsc);
            }
        }
    }

【2】 第一步中我们通过读取配置文件获取了DataSourceConfig集合
这一步 分别遍历集合,把DataSourceConfig转成ActiveRecordPlugin集合,在JbootAppConfig文件就可以完成model到表的映射
tips:在多数据源的请开个,这里的映射其实是有很多问题的。需要分别指定每个数据源对应的表名。
jboot.datasource.a1.table=user,xxx
这里其实可以换一种思路来扫描Model类,可以通过指定包名。或者ant风格

private ActiveRecordPlugin createRecordPlugin(DataSourceConfig config) {

        String configName = config.getName();
// 分库分表的实现也是这里
        DataSource dataSource = new DataSourceBuilder(config).build();
// 从数据库配置
        String configTableString = config.getTable();
        String excludeTableString = config.getExcludeTable();

        ActiveRecordPlugin activeRecordPlugin = StringUtils.isNotBlank(configName)
                ? new ActiveRecordPlugin(configName, dataSource)
                : new ActiveRecordPlugin(dataSource);

        /**
         * 不需要添加映射的直接返回
         */
        if (!config.isNeedAddMapping()) {
            return activeRecordPlugin;
        }

/**
  * 处理逻辑:扫描所有带有@Table注解的model子类,筛选需要的结果
  * configTableString 是需要映射的表名  (若没有配置,默认都会通过)
  * excludeTableString  是需要排除的表名
  * 返回需要映射的表信息
  */
        List<TableInfo> tableInfos = TableInfoManager.me().getTablesInfos(configTableString, excludeTableString);
        if (ArrayUtils.isNullOrEmpty(tableInfos)) {
            return activeRecordPlugin;
        }

        for (TableInfo ti : tableInfos) {
            if (StringUtils.isNotBlank(ti.getPrimaryKey())) {
                activeRecordPlugin.addMapping(ti.getTableName(), ti.getPrimaryKey(), (Class<? extends Model<?>>) ti.getModelClass());
            } else {
                activeRecordPlugin.addMapping(ti.getTableName(), (Class<? extends Model<?>>) ti.getModelClass());
            }
        }

        return activeRecordPlugin;
    }

model其余部分也是沿用了jfinal Db+record模式

sharding-jdbc

github:https://github.com/shardingjdbc/sharding-jdbc
分库分表,读写分离的框架。待补充。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容