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文件
扫描jar文件里面的类(URLClassLoader)
ClassScanner 类的 findJars方法
URL[] urLs = urlClassLoader.getURLs();
这里涉及到JVM的类加载原理,先找到java_home,再依次加载所有依赖的类。后期也需要深入学习一下。
model部分
@Table注解
jfinal 需要手动子在配置类里面实现表名到Model的映射。jboot提供了@Table注解。
来看一下从数据源到model映射的流程
1.JbootAppConfig类中初始化JbootDbManager类对象,直接获取类成员变量集合(activeRecordPlugins)
List
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
【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 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
分库分表,读写分离的框架。待补充。