JFinal
注:我这里只是记了下实际应用,很多细节上的东西没写进来,想学习JFinal的话,可以一边参考JFinal官方文档一边参考我这个,当然,水平有限,只做了一些简单的入门级代码
JFinal基本配置
内置Jetty启动项目:
Jfinal推荐使用WebRoot\WEB-INF\classes放class文件,
于是创建项目的时候Default output folder位置改成上面,然后Content directory要与此对应。,当然也可以用tomcat来使用,跟SSH的新建dynamic Web Project一样新建出来就可;
添加JFinal的Controller过滤器
<filter> <filter-name>jfinal</filter-name> <filter-class>com.jfinal.core.JFinalFilter</filter-class> <init-param> <param-name>configClass</param-name> <param-value>demo.DemoConfig</param-value> </init-param> </filter> <filter-mapping> <filter-name>jfinal</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
创建配置文件,是个java文件
package demo; import com.jfinal.config.*; import com.jfinal.plugin.activerecord.ActiveRecordPlugin; import com.jfinal.plugin.c3p0.C3p0Plugin; import com.jfinal.render.ViewType; public class DemoConfig extends JFinalConfig { /** * 此方法用来配置 JFinal 常量值, 如开发模式常量 devMode 的配置, 默认视图类型 ViewType的配置 * <p> * JFinal 会对每次请求输出报告,如输出本次请求的 Controller、Method 以及请求所携带的参数。JFinal 支持 JSP、FreeMarker、Velocity 三种常用视图。 */ @Override public void configConstant(Constants me) { me.setDevMode(true); //设置运行在开发模式下的默认视图类型为FREE_MARKER me.setViewType(ViewType.FREE_MARKER); } /** * 此方法用来配置 JFinal 访问路由(路径),如下代码配置了将”/hello”映射到 HelloController 这个控制 器 , 通 过 以 下 的 配 置 , http://localhost/hello 将 访 问 HelloController.index() 方 法 , 而http://localhost/hello/methodName 将访问到 HelloController.methodName()方法。 */ @Override public void configRoute(Routes me) { //add表示添加了一个控制器,路径为/,进入该控制器将默认访问该控制器的index方法,HelloControl为处理该请求路径的类。 //加入要访问该控制器下的其他请求,那么在输入http输入/hello/test,就访问到该控制器中的test方法 me.add("/", HelloControl.class); // me.add("/hello/test",HelloControl.class); } /** * 此方法用来配置 JFinal 的 Plugin, 如下代码配置了 C3p0 数据库连接池插件与 ActiveRecord * 数据库访问插件。通过以下的配置,可以在应用中使用 ActiveRecord 非常方便地操作数据库 */ @Override public void configPlugin(Plugins me) { loadPropertyFile("config.txt"); C3p0Plugin c3p0Plugin = new C3p0Plugin(getProperty("jdbcURL"), getProperty("user"), getProperty("password") ); me.add(c3p0Plugin); ActiveRecordPlugin arp = new ActiveRecordPlugin(c3p0Plugin); //显示sql语句 arp.setShowSql(true); me.add(arp); //添加数据库与Model的关系映射,User需要继承Model<>类 arp.addMapping("user", User.class); } @Override public void configInterceptor(Interceptors me) { } /** * 此方法用来配置JFinal的Handler, 如下代码配置了名为ResourceHandler的处理器, Handler * 可以接管所有 web 请求,并对应用拥有完全的控制权,可以很方便地实现更高层的功能性扩 * 展 */ @Override public void configHandler(Handlers me) { } }
HelloControl控制器
package demo; import com.jfinal.core.ActionKey; import com.jfinal.core.Controller; public class HelloControl extends Controller { public void index() { String msg = "Welcome To JFinal World"; //将信息传递到页面,页面中读取代码为: //<!--为什么有!'',意思是如果读取到的参数为null,使用''代替, //因为Freemarker需要安全输出,页面中如果有Null会报错--> //<h1>${(helloworld)!''}</h1> setAttr("helloworld", msg); //使用FreeMarker渲染页面 renderFreeMarker("helloworld.html"); } //该注解是直接映射,意思测试的时候使用localhost/test就会访问到该路径,当然,服务器端口使用的80 @ActionKey("test") public void test() { renderFreeMarker("/helloworld.html"); } }
有个小技巧,在html页面中,可以使用
<#include “xxx.html”>
可以将xxx.html里面的东西包含到你需要的页面,例如修改和删除都使用同一个页面的情况下,就可以用的到。
Jfinal MVC及参数的接收,以及数据源配置
数据源配置:
数据源配置即配置数据库连接,在这里使用的mysql,以及使用外置文件配置相关连接参数,url,username等;
在jfinal中,这种配置类似一种插件,可插拔,所以放在了配置文件中的
configPlugin(Plugins me方法中)
/** * 此方法用来配置 JFinal 的 Plugin, 如下代码配置了 C3p0 数据库连接池插件与 ActiveRecord * 数据库访问插件。通过以下的配置,可以在应用中使用 ActiveRecord 非常方便地操作数据库 */ @Override public void configPlugin(Plugins me) { //加载配置文件 loadPropertyFile("config.txt"); //C3P0数据源的初始化 C3p0Plugin c3p0Plugin = new C3p0Plugin(getProperty("jdbcURL"), getProperty("user"), getProperty("password") ); me.add(c3p0Plugin); ActiveRecordPlugin arp = new ActiveRecordPlugin(c3p0Plugin); //可插拔,添加数据库和对象之间的数据源 me.add(arp); //映射数据库,user是表名,表默认主键是id,后面参数是实体,将实体和表映射,注意,User继承了Jfinal的Model<User(M)>类,是个泛型类 arp.addMapping("user", User.class); }
关于Jfinal中Model的配置;
Model只需要继承一个类,不用设置属性,属性是从数据库里面映射过来,数据库的列名是什么,这个model对应的属性就是什么
package demo; import com.jfinal.plugin.activerecord.Model; public class User extends Model<User> { /** * */ private static final long serialVersionUID = 1L; public static User dao = new User();
表单参数的接收
如果使用get方法的RESTFul风格,那么有可能出现问题,在使用这样的 getParaToInt(“id”)的时候
Jfinal中接收表单参数的方法有几种,一种是getPara(“表单name”),一种是使用对象来接收参数,类似Spring MVC的控制器,
在控制器中某表单提交去向对应的方法,我这给的是test,
public void test() { //这个是最简单的方法,通过getPara // getPara("表单名"); //接受传过来的参数,刚好是某个对象的属性,使用getModel来取得表单参数,getModel有2种重载,一种是单个参数,一种是两个参数,单个参数的如下,在表单域里面输入框的name,例如我这使用的是User.class,那么在那里就写user.attribute,对象.属性的方法来作为name,在表单页面的输入框name为User,首字母小写就对应这个对象, User user = getModel(User.class); //第二种重载,getModel(User.class,"u"),就是起个别名,在表单里面的name就为u.attribute,就可以接收参数了。 User user1 = getModel(User.class, "u"); //取出user的某个属性,还有getInt,getDate等方法来获取对应类型的属性 System.out.println(user.getStr("name")); //这个方法是渲染视图,上面讨论过。如何传数据从控制器到页面呢?使用setAttr("参数名",参数值)这个方法来将参数放到request域里面传递到页面 renderFreeMarker("/helloworld.html"); }
JFinal之CRUD
Model书写:
对象的属性是从数据库直接获取的,假如数据库列名是name,那么当我们在取name这个属性的时候,在页面,就直接${user.name}
package demo; import com.jfinal.plugin.activerecord.Model; public class User extends Model<User> { private static final long serialVersionUID = 1L; public static User dao = new User(); }
查询
使用freemaker渲染页面,html页面如下书写,接收服务器传过来的数据(未分页版本)
页面书写:
其中 userList是从服务器返回来的数据,是一个集合,这里做的是遍历。list表示遍历这个集合 <!--假如是分页查询,那么应该这么写 <#list userList.list as user> --> <#list userList as user> <tr> <td> ${(user.id)!''} </td> <td> ${(user.name)!''} </td> <td> ${(user.address)!''} </td> <td> ${(user.phone)!''} </td> <td> 这个是查询单条的访问路径 <a href="/userid/${(user.id)!''}">查看</a> </td> </tr> </#list> 分页查询输出当前页码,总记录数,总页数; 总共${userList.totalRow}条记录,当前第${pageNum}页,总共${userList.totalPage}页 <#--一个简单的分页控制器,顺便用了下freemaker的if使用,freemaker要使用#才能注释,注意,条件判断之后不要有空格,因为不会自动过滤空格--> <p><a href="/userPage/1">首页</a> <a href="/userPage/<#if pageNum == 1>1 <#else>${pageNum-1} </#if>">上一页</a> &ebsp; <a href="/userPage/<#if pageNum == userList.totalPage>${userList.totalPage} <#else>${pageNum+1} </#if>">下一页</a> <a href="/userPage/${userList.totalPage}">末页</a>
控制器书写:
/** * 查询所有 */ public void user() { String sql = "select * from user order by id desc"; setAttr("userList", User.dao.find(sql)); renderFreeMarker("/helloworld.html"); } /** * 查询单条记录 */ public void userid() { //截取id,注意,这种是RESTFul风格的获取get方式传参的手段,假如使用getParaToInt("id"),url为userid/1,的时候,获取不到值,那么这里也支持问号传参,userid?id=xxx,这样就可以使用getParaToInt("id")来取得id int id = getParaToInt(0); //根据id查询用户 User user = User.dao.findById(id); //还可以这样写,下面参数第二个开始是一个Object... params,不定参数 User user = User.dao.findFirst("select * from user where id = ? ", id); //显示查询到的用户信息 System.out.println(user); } /** * 分页查询,Jfinal提供了方法 * paginate(pageNumber, pageSize, 需要查的映射(select *), sql语句,从from开始, 用以替换?的参数) * 最后一个参数可要可不要,因为是不定参数。 */ public void userPage() { String sql = "from user"; int pageNum = getParaToInt("pageNo", 1); setAttr("pageNum", pageNum); setAttr("userList", User.dao.paginate(pageNum, 2, "select *", sql)); renderFreeMarker("helloworld.html"); }
增加
页面书写:
<form action="addUser"> <input type="text" name="user.name"> <input type="text" name="user.phone"> <input type="submit" value="提交"> </form>
控制器书写:
public void addUser() { //从页面获取user的属性 User user = getModel(User.class); //save()方法放返回一个布尔值,如果添加成功返回true,在这里我们添加成功的话就重定向到user列表页面,失败的话就渲染一个纯文本页面,renderText boolean flag = user.save(); if (flag) { redirect("/user/"); } else { renderText("Sorry,有异常,插入失败"); } }
删除
控制器书写:
public void deleteUser() { //取得例如deleteUser/1-2-3中第一个参数1,加入使用getParaToInt(1);则取到的是第二个参数2 int id = getParaToInt(0); boolean flag = User.dao.deleteById(id); if (flag) { redirect("/user/"); } else { renderText("Sorry,有异常,删除失败"); } }
页面书写:
<#list userList as user> <tr> <td> ${(user.id)!''} </td> <td> ${(user.name)!''} </td> <td> ${(user.address)!''} </td> <td> ${(user.phone)!''} </td> <td> <a href="/deleteUser/${(user.id)!''}">删除</a> </td> </tr> </#list>
修改
页面书写:
页面其实跟添加页面差不多,只是action的时候带过去id,在这里是做了一个隐藏域,当然也可以在表单的action中updateUser/${(user.id)!”}这样来传递ID
<form action="updateUser"> 用户名<input type="text" name="user.name"><br> 电话号码<input type="text" name="user.phone"><br> 地址<input type="text" name="user.address"><br> id<input type="hidden" name="user.id"><br> <input type="submit" value="提交"> </form>
控制器书写
/** * 修改用户 */ public void updateUser() { User user = getModel(User.class); boolean flag = user.update(); if (flag) { redirect("/user/"); } else { renderText("Sorry,有异常,修改失败"); } }
Interceptor拦截器
拦截器:JFinal AOP的实现方式,拦截器并非线程安全,县城安全拦截器需要继承PrototypeInteceptor来实现
拦截器有三个级别,Global,Controller,Action,Global对所有Action进行拦截;Controller就是一整个控制器,包含了多个Action级, Action级就是具体到某个Action的拦截
Interceptor通过注解来实现,支持的注解为@Before(xxxInterceptor.class)
这个xxxInterceptor需要实现Interceptor接口
public class DemoActionInterceptor implements Interceptor { @Override public void intercept(Invocation inv) { Controller c = inv.getController(); System.out.println("这个是拦截器在业务方法前执行的代码"); inv.invoke();//让被拦截的方法继续执行。 System.out.println("这个是拦截器在业务方法后执行的代码部分"); } }
全局Interceptor的配置:在Config里面的configInterceptor中配置,而不使用注解,代码如下。
public void configInterceptor(Interceptors me) { me.add(new DemoInterceptor());//定义一个全局拦截器 }
Controller级别Interceptor的配置:其实就是将Before放在类上,
@Before({DemoInterceptor.class,OtherInteceptor.class}) public class HelloControl extends Controller { public void index() {} }
Action级别的拦截器:其实就是放在Action上面,即Controller里面的某个方法
权限验证拦截器
public class AuthInterceptor implements Interceptor { public void intercept(ActionInvocation ai) { Controller controller = ai.getController(); User loginUser = controller.getSessionAttr("loginUser"); if (loginUser != null && loginUser.canVisit(ai.getActionKey())) ai.invoke(); else controller.redirect("/login.html");
思考Jfinal中Service层及AOP实现
研究了3-4个小时,一直在纠结Interceptor和AOP,SSH中的AOP是放在了Service中的方法上,于是我按照同样的思路想在jfinal中搞个service层,然后给其加上AOP,后来发现不可行,报了这么个错误;
This method can only be used for action interception
意思这个方法只能用于Action拦截,深层意思就是只能用在控制器Controller上,而不能用在自己定义的Service上。
意味着jfinal的AOP都是实现在控制器级别的,例如这样:
@Before(DemoActionInterceptor.class) public void test1() { //这个enhance就是返回一个带了事务(@Before(Tx.class))的Service对象 UserService userService = enhance(UserService.class);//还可以上第二个参数,Tx.class,Inject拦截注入一个事务给service,就可以不用在service里面来Before了 User user = userService.login("老大", "asd"); if (user != null) { System.out.println("业务实体返回了一个DTO:" + user); renderText("asd"); } else { renderText("用户名或密码错误"); } }
业务层代码:
//@Before(DemoActionInterceptor.class如果加了这句话会报运行时错误。 public class UserService { @Before(Tx.class) public User login(String username) { //调用dao,jfinal中model其实就是一个dao User u = User.dao.findFirst("select * from user where name=?", username); if (u != null && password.equals(u.getStr("password"))) { return u; } else { return null; } } }
实践表明,Interceptor不能单独的应用在S层,必须应用在C层,为什么这样呢,看了下Jfinal大大对于这个问题给的回复,恍然大悟,。以下是大大的设计思路
- AOP 本来就是独立出来的,业务层不需要知道 AOP 在哪里,AOP紧靠service或controller在本质上并无差别,如果要找差别的话:==紧靠service层做事务 AOP会让事务开启的时间稍晚一些,带来略微的性能提升,其实controller中的代码是简单的控制代码所耗性能对于业务层来说可以忽略不计,所以在 controller 上做声明式事务是jfinal权衡后最佳的选择。==
AOP 希望贴近 service 来做是理论化、学术化的诉求,通常软件开发是工程性的活动,理论化与学术化不经济也不实用。
So,我们的S层仅仅只需要事务支持,(事务支持其实可以简单理解成就是数据库操作的多次操作,然后事务提交,提交失败回滚),而不做AOP,AOP放在了Controller来进行操作,假如需要的话。
业务逻辑层放哪里?
这个问题也是思考了半天,最后咨询得出,有两个方案,一个方案是放在Model里面,一个方案也是单独抽一个Service层出来,SSH的思路是S层调用dao对象,所以可以在Service中使用Model.dao来调用dao对象(Model.dao是一个静态的已经初始化号的Model对象,当然这个Model继承了Jfinal提供的Model<xxxx>
)
PS:没想到这么快就接触到了领域模型,充血模型,,我还以为我要在事务脚本摸爬滚打许久;
表关联映射
Jfinal中表关联映射有两种,一种是通过sql语句的联表查询来进行关联,什么inner join,left join之类的,跟sql查询差不多。
public void relation() { String sql = "select b.*, u.user_name from blog b inner join user u on b.user_id=u.id where b.id=?"; Blog blog = Blog.dao.findFirst(sql, 123); String name = blog.getStr("user_name"); }
另外一种就是Jfinal特有的
理解思路:加入有两个对象,一个User,一个Blog,一对多关系,一个User有多个Blog,一个Blog只有一个User。
思路即:查询方法直接写好,getUser(),传入Blog中的表示User的外键user_id来查询Blog对应的User;
在User中,用Blog.dao来查询,传入User的id来查询该User拥有的Blog集合
import com.jfinal.plugin.activerecord.Model; public class Blog extends Model<Blog> { public static final Blog dao = new Blog(); public User getUser() { return User.dao.findById(get("user_id")); } } public class User extends Model<User> { public static final User dao = new User(); public List<Blog> getBlogs() { return Blog.dao.find("select * from blog where user_id=?", get("id")); } }
ActiveRecord
这个部分包含了Model以及Db+Record的模式来替代Model;
两者用法区别不大,唯一的区别,在于不需要建立实体类,例如,之前使用dao的时候会建立一个继承Model的model,然后内部实例化该对象来当dao使用,那么在使用Db+Record的时候就可以采用输入表名的手段来区别实体对象,就不用单独建立实体类;代码区别示例
这个是Model //创建name属性为James,age属性为25的User对象并添加到数据库 new User().set("name", "James").set("age", 25).save(); //删除id值为25的User User.dao.deleteById(25); //查询id值为25的User将其name属性改为James并更新到数据库 User.dao.findById(25).set("name", "James").update(); //查询id值为25的user, 且仅仅取name与age两个字段的值 User user = User.dao.findById(25, "name, age"); //获取user的name属性 String userName = user.getStr("name"); //获取user的age属性 Integer userAge = user.getInt("age"); //查询所有年龄大于18岁的user List<User> users = User.dao.find("select * from user where age>18"); // 分页查询年龄大于18的user,当前页号为1,每页10个user Page<User> userPage = User.dao.paginate(1, 10, "select *", "from user where age > ?", 18);
这个是DB+Record
// 创建name属性为James,age属性为25的record对象并添加到数据库 Record user = new Record().set("name", "James").set("age", 25); Db.save("user", user); // 删除id值为25的user表中的记录 Db.deleteById("user", 25); // 查询id值为25的Record将其name属性改为James并更新到数据库 user = Db.findById("user", 25).set("name", "James"); Db.update("user", user); // 查询id值为25的user, 且仅仅取name与age两个字段的值 user = Db.findById("user", 25, "name, age"); // 获取user的name属性 String userName = user.getStr("name"); // 获取user的age属性 Integer userAge = user.getInt("age"); // 查询所有年龄大于18岁的user List<Record> users = Db.find("select * from user where age > 18"); // 分页查询年龄大于18的user,当前页号为1,每页10个user Page<Record> userPage = Db.paginate(1, 10, "select *", "from user where age > ?", 18);
关于Record的事务
说到事务,其实底层就是把
jdbc的Connection conn.autoCommit(false)设置为false,
然后进行jdbc几个操作一起进行,捆绑进行,要不都完成成功,要不都失败,然后开始
Connection conn.commit()开始提交
,提交成功就都成功,失败就回滚,一般来个try catch,抓到异常后进行事务回滚,
session.rollback();事务回滚
编程式事务:
boolean succeed=Db.tx(new IAtom(){ public boolean run()throws SQLException{ //数据操作1 int count=Db.update("update account set cash = cash - ? where id = ?",100,123); //数据操作2 int count2=Db.update("update account set cash = cash + ? where id = ?",100,456); return count==1&&count2==1; }});
声名式事务:
其实跟之前的事务差不多,来个注解
// 本例仅为示例, 并未严格考虑账户状态等业务逻辑 @Before(Tx.class) public void trans_demo() { // 获取转账金额 Integer transAmount = getParaToInt("transAmount"); // 获取转出账户id Integer fromAccountId = getParaToInt("fromAccountId"); // 获取转入账户id Integer toAccountId = getParaToInt("toAccountId"); // 转出操作 Db.update("update account set cash = cash - ? where id = ?", transAmount, fromAccountId); // 转入操作 Db.update("update account set cash = cash + ? where id = ?", transAmount, toAccountId); }
Cache缓存
缓存就是什么,就是你经常要查的东西来放到缓存里,因为开启数据库是个代价比较高昂的操作,所以应用缓存来减少数据库开和关的次数来提升性能,类似连接池,空间换时间;
一般缓存里放的东西都是经常查但又不经常修改的数据,因为缓存可以设置缓存内容失效时间,可以相对也比较灵活。
在Jfinal中默认使用的Cache是EhCache,使用方法有几个地方
1:添加相关jar报,包括
- ehcache-core
- log4j
- slf4j-api
- slf4j-log4j12
2:在Config类中
public void configPlugin(Plugins me){ me.add(new EhCachePlugin()) }
3:拷贝ehcache.xml文件推荐放到src下或者WEB-INF下(其实放哪里都可以,名字叫这个就行);
4:对Action进行注解
@Before(CacheInterceptor.class) //jfinal框架带的 //@CacheName("userPage")自己定义使用缓存的目录,如果没有这个注解的话,默认actionKey为缓存Action public void userPage(){ System.out.println("这个是主体方法"); String sql = "from user"; int pageNum = getParaToInt("pageNo",1); setAttr("pageNum", pageNum); setAttr("userList", User.dao.paginate(pageNum, 2, "select *", sql)); renderFreeMarker("helloworld.html"); }
5:以上代码运行还会报错,还需要在ehcache中配置,如下:配置cachename与java文件中的cachename或者actionKey一致,注意,包括路径映射路径,假如我使用了如下/hello/test,那么我的cacheName就应该为/hello/test/userPage
;
config中的映射
@Override public void configRoute(Routes me) { //add表示添加了一个控制器,路径为/hello,HelloControl为处理该请求路径的类。 //加入要访问该控制器下的其他请求,那么在输入http输入/hello/test,就访问到该控制器中的test方法 me.add("/",HelloControl.class); //me.add("/hello/test",HelloControl.class);
XML配置,主要是<cache name>
部分,顺便添加个配置说明
<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="false" monitoring="autodetect" dynamicConfig="true"> 磁盘缓存位置 <diskStore path="java.io.tmpdir"/> 默认缓存配置 <defaultCache maxEntriesLocalHeap="10000" eternal="false" overflowToDisk="true" timeToIdleSeconds="20" timeToLiveSeconds="60"> </defaultCache> <cache name="/userPage" 堆内存中最大缓存对象书,0没有限制 maxEntriesLocalHeap="10000" 磁盘中的最大对象书,默认为0不限制 maxEntriesLocalDisk="1000" 是否永久有效,如果为true,timeouts被忽略,永不过期 eternal="false" 当调用flush()是否清除缓存,默认是 overflowToDisk="true" 磁盘缓存的缓存区大小,每个Cache都有自己的一个缓冲区 diskSpoolBufferSizeMB="20" 失效前的空闲秒数,当eternal为false时,这个属性才有效 timeToIdleSeconds="300" 失效前的存活秒数,创建时间到失效时间的时间间隔为存活时间, timeToLiveSeconds="600" 内存回收策略:使用频率最低:LFU,Less Frequently Used,最近最少使用LRU,Least Recently Used,先进先出,FIFO,Firsh in First Out memoryStoreEvictionPolicy="LFU" transactionalMode="off" 是否缓存虚拟机重启期数据 diskPersistent="true" 磁盘失效线程运行时间间隔,默认为120秒 diskExpiryThreadIntervalSeconds="1" /> </ehcache>
EvictInterceptor
用来修改了数据库信息后实时更新信息;用法如下:
在使用了缓存的查询方法上
@Before(CacheInterceptor.class) @CacheName("/userPage") public void userPage(){ System.out.println("这个是主体方法"); String sql = "from user"; int pageNum = getParaToInt("pageNo",1); setAttr("pageNum", pageNum); setAttr("userList", User.dao.paginate(pageNum, 5, "select *", sql)); renderFreeMarker("helloworld.html"); }
假如现在添加了一个用户,不清楚缓存则我们看到的数据不能显示新添加的这个用户,那么需要如下操作
@Before({LoginValidator.class,EvictInterceptor.class}) @CacheName("/userPage") public void addUser() { User user = getModel(User.class); boolean flag = user.save(); if (flag) { redirect("/userPage/"); } else { renderText("Sorry,有异常,插入失败"); } }
Validator,验证器
Validator实际上就是一个Inteceptor,配置地方差不多,在使用的时候,建立一个类,然后继承Validator,里面有两个方法,一个是校验方法,一个是出错后的处理方法
public class LoginValidator extends Validator { /** * 用于参数校验的方法 */ @Override protected void validate(Controller c) { /* field:需要校验的字段,例如页面输入框的name是user.name,那么这里就是user.name errorKey:出错了的时候返回错误信息,在页面取错误信息所用的键 errorMessage:错误的具体信息 */ //validateRequiredString("field", "errorKey", errorMessage); validateRequiredString("name", "nameMsg", "请输入用户名"); validateRequiredString("pass", "passMsg", "请输入密码"); } /** * 用于参数校验未通过产生错误的时候处理错误的方法 */ @Override protected void handleError(Controller c) { //取得actionKey之后用以判断是添加出错还是更新出错 String actionKey = getActionKey(); String view = null; c.keepModel(User.class);//保存上一次提交的表单记录 //失败后返回 System.out.println("actionkey:" + actionKey); if("/addUser".equals(actionKey)){ System.out.println("这个是添加"); view = "/addUser.html"; } if("/doUpdateUser".equals(actionKey)){ System.out.println("这个是更新"); view = "/updateUser.html"; } c.renderFreeMarker(view); } }
在控制器中,主要就是添加了一个Validator注解:
@Before({LoginValidator.class,EvictInterceptor.class}) @CacheName("/userPage") public void addUser() { User user = getModel(User.class); boolean flag = user.save(); if (flag) { redirect("/userPage/"); } else { renderText("Sorry,有异常,插入失败"); } }
在页面中,主要就是需要来接收服务器传递过来的错误信息
用户名<input type="text" name="user.name" value="${(user.name)!''}"> ${(nameMsg)!''} <br> 电话号码<input type="text" name="user.phone" value="${(user.phone)!''}"> ${(phoneMsg)!''} <br> 地址<input type="text" name="user.address" value="${(user.address)!''}"><br> <input type="hidden" name="user.id" value="${(user.id)!''}"><br> <input type="submit" value="保存">
文件上传
tomcat重新更新,启动的时候会重新部署项目,那么上传的文件可能丢失
文件上传需要一个jar包支持,当然在jfinal里面集成了
- cos-26Dec2008.jar
文件上传的思路其实就是表单提交的时候添加enctype=”multipart/form-data”属性,method为POST;控制器方面使用getFile(“fileName”)来提取上传的文件,然后jfinal默认文件上传到WebRoot(WebContent根目录下)的upload目录中,当然可以修改。在config中;
代码如下:
页面
<form action="upload/doUpload" method="POST" enctype="multipart/form-data" > <input type="text" name="title"><br><br> <input type="file" name="filename"><br> <input type="submit" value="上传"> </form>
控制器
package demo; import java.io.File; import java.util.UUID; import com.jfinal.core.Controller; import com.jfinal.upload.UploadFile; public class UploadController extends Controller { /** * 去向文件上传页面 */ public void index(){ render("/upload.html"); } /** * 执行文件上传操作,加了个try catch是为了 方便调试,不然出错都不知道出错在哪里,实际使用是不加,注意,如果是tomcat服务器启动的项目,文件会上传到eclipse中tomcat的部署目录去eclipse中是看不到的,但是服务器能访问到,如果是jetty的话,就能直接在eclipse中看到 * 或者来个全局Inteceptor */ public void doUpload(){ try{ //如何获取带有文件上传的表单中的非文件元素数据 //String title = getPara("title"); // 这个方法不可行了,那么必须要先得到getFile,才能得到相关数据,因为multipart请求要求先解析 UploadFile file = getFile("filename"); //取得文件扩展名 String ext = file.getFileName().substring(file.getFileName().lastIndexOf(".")); //文件重命名 file.getFile().renameTo(new File(file.getSaveDirectory()+UUID.randomUUID()+ext)); // System.out.println(file.getSaveDirectory()); String title = getPara("title"); System.out.println(title); renderText("success"); } catch(Exception e){ e.printStackTrace(); } catch(Error s){ s.printStackTrace(); } } }
配置文件
@Override public void configConstant(Constants me) { me.setDevMode(true); //设置运行在开发模式下的默认视图类型为JSP me.setViewType(ViewType.FREE_MARKER); me.setMaxPostSize(10*1024*1024); //单位是字节,1*1024代表1KB,*1024代表1m //设置文件上传路径 me.setUploadedFileSaveDirectory("jfinalDemoUpload");//可以写绝对路径:当然\分隔符是\\ }
摘自:https://blog.csdn.net/dartagnan_wang/article/details/48941335/