在非web环境下对jfinal Controller 进行单元测试
所有对Controller的测试必须继承ControllerTestCase,此类中方法说明如下
use | 需要调用的url |
post | post数据包,支持String和File |
writeTo | response数据写入文件 |
invoke | 调用url |
findAttrAfterInvoke | action调用之后getAttr的值 |
---|
public class PostTestCase extends ControllerTestCase<Config> {
@Test
public void line() throws Exception {
String url = "/post";
String filePath = Thread.currentThread().getContextClassLoader().getResource("dataReq.xml").getFile();
String fileResp = "/home/kid/git/jfinal-ext/resource/dataResp.xml";
String resp = use(url).post(new File(filePath)).writeTo(new File(fileResp)).invoke();
System.out.println(resp);
}
@Test
public void test3() {
String url = "/post/1?age=1&age=2&name=2";
String body = "<root>中文</root>";
use(url).post(body).invoke();
}
}
扫描clsspath和lib中继承了model的类自动注册,可选择不同的命名规则自定映射表名,也可以在每一个model上用注解指定表名
AutoTableBindPlugin 继承自ActivitiRecordPlugin,所以如果你使用 AutoTableBindPlugin则不用再使用ActivitiRecordPlugin,其他需要在 ActiviRecordPlugin中的设置都在此plugin中设置,比如方言,大小写不敏 感等..
DruidPlugin druid = new DruidPlugin("jdbc:mysql://127.0.0.1/jfinal_demo", "root", "root");
AutoTableBindPlugin atbp = new AutoTableBindPlugin(druid);
*记住一定要在启动atbp之前启动连接池的plugin
如果你需要载入jar包中的model,需要调用以下的api将jar包加入扫描
atbp.addJar("modelInJar.jar");
atbp.addJars("jar1,jar2");
一个model自动绑定的表明默认是该类的simpleClassname,如果需要用其他 的命名风格,需要在构造函数中指定,如
AutoTableBindPlugin atbp = new AutoTableBindPlugin(cp,SimpleNameStyles.LOWER);
SimpleNameStyles 中已有的命名风格以及映射的表明如下表:
DEFAULT | FIRST_LOWER | UP | LOWER | UP_UNDERLINE | LOWER_UNDERLINE | |
DevInfo.java | DevInfo | devInfo | DEVINFO | devinfo | DEV_INFO | dev_info |
ParamNameStyles 含有构造参数的命名风格:
module(test) | lowerModule(test) | upModule(test) | upUnderlineModule(test) | lowerUnderlineModule(test) | |
DevInfo.java | test_DevInfo | test_devinfo | test_DEVINFO | test_DEV_INFO | test_dev_info |
如果有Model到表的映射命名不符合规范需要单独配置,则在Model上加上 TableName注解,属性说明如下:
tableName | 表名 | 必填 |
pkName | 外键名 | 默认”” |
configName | 数据源名 | 默认”” |
如果你只想用注解而不想让没有注解的model被自动注册,则如下使用
atbp.setAutoScan(false);
如果你打开了自动扫描,但是又有不想要被扫描进去的,如通用的BaseModel
atbp.addExcludeClass(Class<? extends Model> clazz)
推荐将不同数据源的Model放在不同的package中然后调用 addScanPackages 设置要扫描的package
atbp = new AutoTableBindPlugin(druidPlugin)
.addScanPackages("com.xx.yy.service1.model");
atbp2 = new AutoTableBindPlugin("another",druidPlugin2)
.addScanPackages("com.xx.yy.service2.model2","com.xx.yy.service2.model3")
如果在一个package里面有属于不同数据源的model(如果不是因为无法避 免的历史遗留原因不要采用这样的方式),那么给Model加上TableBind注 解并给configName属性赋值
类似ibatis的在xml中管理sql.主要用于复杂的sql管理或者有dba的开发团 队
插件会扫描classpath根目录下以”.sql”结尾的xml文件. 用一个xml举例,文件名为user-sql.xml,内容如 下:
<sqlGroup name="blog" >
<sql id="findBlog">select * from blog</sql>
<sql id="findUser">select * from user</sql>
</sqlGroup>
插件会将name+id作为一个sql语句的唯一标识, 在java中获取该sql的方法为 SqlKit.sql(“blog.findBlog”)
如果你需要处理某个消息号对应的消息,需要实现com.jfinal.plugin.jms.ReceiveResolver
public class AReceiveResolver implements ReceiveResolver {
@Override
public void resolve(Serializable objectMessage) throws Exception {
System.out.println("AReceiveResolver");
}
}
- 示例 JmsKit.sendQueue(“q1”, new M(), “a”);
- 接口 public static boolean sendQueue(String queueName, Serializable
message, String msgName)
- 参数说明
queueName message msgName 发送队列的名字 发送的消息对象 发送的消息名字
################################
# server info #
################################
# jms服务器地址
serverUrl=tcp://localhost:61616
username=system
password=manager
################################
# queue info #
################################
# 发送的队列名字,用“,”号分隔
sendQueues=q1,q2
# 接受的队列的名字,用“,”号分隔
receiveQueues=q1,q3
# 队列q1上消息名字为a的消息号
queue.q1.a=10000
#接受到队列q1上消息名字为a的消息的时候调用的处理器
queue.q1.a.resolver=test.com.jfinal.plugin.jms.AReceiveResolver
queue.q1.b=20000
queue.q1.b.resolver=test.com.jfinal.plugin.jms.BReceiveResolver
################################
# topic info #
################################
sendTopics=t1,t2
receiveTopics=t1,t3
topic.t1.c=30000
topic.t1.c.resolver=test.com.jfinal.plugin.jms.CReceiveResolver
topic.t3.d=40000
topic.t3.d.resolver=test.com.jfinal.plugin.jms.DReceiveResolver
需要进行的调度任务实现必须实现org.quartz.Job接口
需要进行的调度任务实现必须实现java.lang.Runnable接口
插件默认加载classpath根目录下job.properties文件。 如果需要加载指定的配置文件,需要在构造方法中传入参数
job.properties配置示例
#JobA
a.job=test.com.jfinal.plugin.quzrtz.JobA
a.cron=*/5 * * * * ?
a.enable=true
#JobB
b.job=test.com.jfinal.plugin.quartz.JobB
b.cron=*/10 * * * * ?
b.enable=false
配置说明 job cron enable为配置关键字 a和b为任务的名字,仅作为标识,无其他用处。
任务名字.job | 调度任务的类全名 |
任务名字.cron | 调度任务的cron表达式 |
任务名字.enable | 调度任务是否启用 |
在plugin上调用add方法
quartz 2.X版本和1.X不兼容. JobDetail 和 CornTrigger 在 1.X版本中是Class,但是在2.X版本中是Interface. QuartzPlugin 解决了兼容问题,默认使用2.X版本,如果需要使用1.X,调用quartzPlugin.version(QuartzPlugin.VERSION_1)即可.
分优先级加载配置文件 在团队开发中如果自己有测试配置需要长期存在但是又不需要提交中心库的时候 可以采用分级配置加载的策略。 如中心库中有config.properties这个配置,你可以创建 config-test.properties文件,配置相同的key,ConfigKit中的方法会优先加载 xx-test.properties文件。
ConfigPlugin configPlugin = new ConfigPlugin();
configPlugin.addResource(".*.properties");
addResource支持正则表达式 当我们加载config.properties时候会找config-test.properties一起加载.
如果我们加载了以下两个配置,下面的测试用例能通过,也就是说当*-test中同 名的key优先读取. config.properties
name=aa
age=1
config-test.properties
name=test
@Test
public void testGetStr() throws InterruptedException {
Assert.assertEquals("test",ConfigKit.getStr("name"));
Assert.assertEquals(1,ConfigKit.getInt("age"));
}
MongodbPlugin 是Jfinal-ext扩展的nosql插件,在MongoKit中封装mongodb 的常用操作。
默认ip和端口
MongodbPlugin mongodbPlugin = new MongodbPlugin("log")
MongodbPlugin mongodbPlugin = new MongodbPlugin("127.0.0.1", 8888, "other");
Map<String, Object> filter = new HashMap<String, Object>();
filter.put("age", "20") ; //精确过滤
Map<String, Object> like = new HashMap<String, Object>();
like.put("name","zhang"); //模糊匹配,相当于sql 的 like %zhang%
Map<String, Object> sort = new HashMap<String, Object>();
sort.put("age","desc"); //排序
Page<Record> page = MongoKit.paginate("sns", 1, 10, filter, like,sort);
MongoKit.save("sns", record) //保存一个record
MongoKit.save("sns", records)//批量保存record
MongoKit.removeAll("sns") //删除所有sns
Map<String, Object> filter = new HashMap<String,Object>();
filter.put("name", "bb");
filter.put("age", "1");
MongoKit.remove("sns", filter); //删除符合条件的sns
Map<String, Object> src = new HashMap<String, Object>();
src.put("age", "1"); //查询条件
Map<String, Object> desc = new HashMap<String, Object>();
desc.put("addr", "test"); //将符合查询条件的文档修改为此文档
MongoKit.updateFirst("sns", src, desc); //只能修改符合条件的第一条数据..
public void save() {
Blog model = getModel(Blog.class);
if (model.getInt("id") == null) {
model.save();
} else {
model.update();
}
render(DwzRender.closeCurrentAndRefresh("pageBlog"));
}
public void edit() {
int id = getParaToInt(0);
Blog blog = Blog.dao.findById(id);
if (id == -1) {
blog = new Blog();
} else if (blog == null) {
render(DwzRender.error("该记录已被删除,请您先刷新列表"));
}
setAttr("blog", blog);
}
public void delete() {
Blog.dao.deleteById(getParaToInt());
render(DwzRender.success());
}
通过list数据生成excel,支持的数据类型为map ,record , model。
通过list数据生成excel,支持多sheet导出,支持的数据类型为map ,record , model.
PoiRender.me(data,data2,...dataN).fileName("your_file_name.xls").headers(headers,headers2,...headerN).cellWidth(5000).headerRow(2)
2003版一个sheet只支持最多65535行,2007则没有此限制.对此PoiRender做了如 下处理
- 默认使用2007版,单个sheet的data无限制,如果想用2003版调用以下API:
PoiRender.me(data).version(PoiKit.VERSION_2003)
- data ,sheetNames,headers,columns长度必须相同.
- 2003版本,如果data长度大于1,会检查每个data的item的数据长度,不允许超 过65535
- 2003版本,如果data长度等于1,如果数据超过65535,会自动拆分为多个sheet
通过list数据生成csv,支持的数据类型为map ,record , model。
对AmCharts报表工具进行了简单的封装
public void pie(){
List<KeyLabel> pies = new ArrayList<KeyLabel>();
KeyLabel e= new KeyLabel("java","111");
pies.add(e);
KeyLabel e2= new KeyLabel("c","11");
pies.add(e2);
render(AmChartsRender.pie(pies, "ampie.swf", "pie_settings.xml",500,500));
}
public void multiple(){
List<String> data = new ArrayList<String>();
data.add("10");
data.add("11");
data.add("12");
data.add("13");
data.add("14");
List<String> data1 = new ArrayList<String>();
data1.add("20");
data1.add("21");
data1.add("22");
data1.add("23");
data1.add("24");
List<List<String>> list = new ArrayList<List<String>>();
list.add(data);
list.add(data1);
List<String> series = new ArrayList<String>();
series.add("1月");
series.add("2月");
series.add("3月");
series.add("4月");
series.add("5月");
render(AmChartsRender.graph(list, series, "amline.swf", "line_settings.xml"));
}
public void simple(){
List<String> data = new ArrayList<String>();
data.add("10");
data.add("11");
data.add("12");
data.add("13");
data.add("14");
List<String> series = new ArrayList<String>();
series.add("1月");
series.add("2月");
series.add("3月");
series.add("4月");
series.add("5月");
render(AmChartsRender.graph(data, series, "amline.swf", "line_settings.xml"));
}public void pie(){
List<KeyLabel> pies = new ArrayList<KeyLabel>();
KeyLabel e= new KeyLabel("java","111");
pies.add(e);
KeyLabel e2= new KeyLabel("c","11");
pies.add(e2);
render(AmChartsRender.pie(pies, "ampie.swf", "pie_settings.xml",500,500));
}
public void multiple(){
List<String> data = new ArrayList<String>();
data.add("10");
data.add("11");
data.add("12");
data.add("13");
data.add("14");
List<String> data1 = new ArrayList<String>();
data1.add("20");
data1.add("21");
data1.add("22");
data1.add("23");
data1.add("24");
List<List<String>> list = new ArrayList<List<String>>();
list.add(data);
list.add(data1);
List<String> series = new ArrayList<String>();
series.add("1月");
series.add("2月");
series.add("3月");
series.add("4月");
series.add("5月");
render(AmChartsRender.graph(list, series, "amline.swf", "line_settings.xml"));
}
public void simple(){
List<String> data = new ArrayList<String>();
data.add("10");
data.add("11");
data.add("12");
data.add("13");
data.add("14");
List<String> series = new ArrayList<String>();
series.add("1月");
series.add("2月");
series.add("3月");
series.add("4月");
series.add("5月");
render(AmChartsRender.graph(data, series, "amline.swf", "line_settings.xml"));
}
利用freemaker生成xml
扫描clsspath和lib中继承了Route的类按照约定的规则自动注册,也可以在 每一个Route上用注解配置
public void configRoute(Routes me) {
me.add(new AutoBindRoutes());
}
如果我们有一个AController,以上代码则相当于
public void configRoute(Routes me) {
me.add("/a",AController.class);
}
默认的注册规则是截取类名Controller前的部分并首字母小写.
如果需要单独配置Route,需要在Controller上加上ControllerBind注解
ControllerBind 注解,属性说明如下:
controllerKey | 访问某个 Controller 所需要的一个字符串 |
viewPath | Controller 返回的视图的相对路径 |
对Controller抛出的异常按照类型进行定制化处理
ExceptionInterceptor exceptionInterceptor = new ExceptionInterceptor();
exceptionInterceptor.addMapping(IllegalArgumentException.class, "/exceptions/a.html");
exceptionInterceptor.addMapping(IllegalStateException.class, "exceptions/b.html");
exceptionInterceptor.setDefault(new ErrorRender("测试系统"));
addMapping 设置一个异常类型对应的View,可以是一个视图路径,也可以是 一个ExceptionRender的子类,类似Controller的 render(String) 和 render(Render)方法 setDefault是当抛出的异常在mapping中没有找到的时候调用的默认的异常处 理方式。
简化国际化页面的render处理。
会在原有的render路径上加上当前请求参数中的country和language。 例如(/p?language=zh&country=CN)
原始view | country | language | 改变之后的view |
/p | zh | CN | /zh_CN/p/index.html |
country 默认值为zh language默认值为CN
ActionReport的人性化版本,用于后台管理系统的日志记录
SysLogInterceptor log = new SysLogInterceptor();
log.addConfig("/blog", new LogConfig("查看博客").addPara("user", "作者名字"));
需要实现以下方法
| process(SysLog sysLog) | 处理一条日志 |
| getUsername(Controller c) | 获取当前登陆用户名 |
| formatMessage(String title, Map<String, String> message) | 格式化日志信息 |
一个简单的日志处理器的实现
public class DefaultLogProccesor implements LogProccesor {
@Override
public void process(SysLog sysLog) {
Map map = BeanUtils.describe(sysLog);
map.remove("class");
Record record = new Record();
record.setColumns(map);
Db.save("syslog", record);
}
@Override
public String getUsername(Controller c) {
User user = c.getSessionAttr("user");
return user.getStr("username");
}
@Override
public String formatMessage(String title, Map<String, String> message) {
String result = title;
if (message.isEmpty()) {
return result;
}
result += ", ";
Set<Entry<String, String>> entrySet = message.entrySet();
for (Entry<String, String> entry : entrySet) {
String key = entry.getKey();
String value = entry.getValue();
result += key + ":" + value;
}
return result;
}
}
上传excel文件解析为model并持久化
<rule>
<sheetNo>1,2<sheetNo> 非必须,默认解析所有sheet.可指定解析对应的sheet,解析多个sheet用","分隔
<start>1</start> 非必须,默认0.解析开始行坐标,从0开始
<end>-1</end> 非必须,默认-1.解析结束行坐标,负数时候从最后一行往回索引.
<postExcelProcessorn>com.xx.xx.AProcessor</postExcelProcessor>
<postListProcessor>com.xx.xx.BProcessor</postListProcessor>
<preExcelProcessor>com.xx.xx.CProcessor</preExcelProcessor>
<preListProcessor>com.xx.xx.DProcessor</preListProcessor>
<cells> 每一个单元格的解析策略
<cell>
<index>1</index> 单元格索引,从0开始
<attribute>en_name</attribute> model属性名
</cell>
<cell>
<index>2</index>
<attribute>cn_name</attribute>
</cell>
<cell>
<index>3</index>
<attribute>explanation</attribute>
</cell>
...
</cells>
</rule>
将jfinal-templates.xml导入eclipse的Preferences-java-Editor-Templates
在任何类中使用,生成logger
protected final Logger logger = Logger.getLogger(getClass());
protected final static Logger logger = Logger.getLogger(Object.class);
在Model中使用,生成dao
public final static Model dao = new Model();
在需要打印日志的变量下面使用
logger.debug("var :" + var);
在需要打印日志的变量下面使用
logger.info("var :" + var);
在需要打印日志的变量下面使用
logger.error("var :" + var);