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> controllerClassList = ClassScanner.scanSubClass(Controller.class);
        if (controllerClassList == null) {
            return;
        }

        for (Class 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文件


    jboot02:controller & model &sharding jdbc_第1张图片
    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 arps = JbootDbManager.me().getActiveRecordPlugins();
2.来看下JbootDbManager的初始化流程。这个初始化就完成了activeRecordPlugins集合的构建

public JbootDbManager() {

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

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

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

                //子数据源的配置
                String shardingDatabase = entry.getValue().getShardingDatabase();
                if (StringUtils.isBlank(shardingDatabase)) {
                    continue;
                }
                Set 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 mergeDatasourceConfigs = new HashMap<>();
        if (allDatasourceConfigs != null) {
            mergeDatasourceConfigs.putAll(allDatasourceConfigs);
        }

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


        for (Map.Entry 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 datasourceNames = new HashSet<>();
        for (Map.Entry 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 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>) ti.getModelClass());
            } else {
                activeRecordPlugin.addMapping(ti.getTableName(), (Class>) ti.getModelClass());
            }
        }

        return activeRecordPlugin;
    }

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

sharding-jdbc

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

你可能感兴趣的:(jboot02:controller & model &sharding jdbc)