introduction
I met JFinal unexpectedly when I was looking at the server container yesterday. Before, my impression of JFinal was just that it was a framework developed by Chinese people to integrate Spring family buckets.
Later I checked it, as if things were not so simple.
JFinal has won the best open source project of OSChina for many years in a row. It's not the integrated Spring family barrel that I understood before, but it has developed a set of WEB + ORM + AOP + Template Engine framework by itself, which is a capital bull!
First look at the introduction of the official warehouse:
This introduction is written in my heart to save you more time, to accompany your lover, family and friends:).
In the field of Manon, who doesn't want to finish the work early and get off work normally, instead of the 996 blessing report every day.
In such an excellent framework, I have never understood it. This is absolutely unacceptable for a Java veteran machine shuttle.
So today, I'll do an open package evaluation of the framework to see if I can save more time and use it well.
This may be the first article in the industry to do a framework evaluation. Let's keep a low profile first: my ability is limited. If there is something wrong with the following, please forgive me.
The next goal is to simply do a Demo and complete the simplest CRUD operation to experience JFinal.
Build project
I opened the official document of JFinal with reverence.
- Document address: https://jfinal.com/doc
I also saw the sample project on the official website. I had to go down and take a look at it. At this time, something that I didn't expect happened. I even had to sign up for it. God, it's 2020. I still have to sign up to download a demo. Am I blind.
Okay, well, you're the boss. You has the final say. Who let me give you my appetite?
The official construction demonstration of the project is eclipse. OK, you win again. I'll follow your steps with idea.
In fact, the process is very simple. It is to create a maven project, and then introduce dependency. The core dependencies are as follows:
<dependency> <groupId>com.jfinal</groupId> <artifactId>jfinal-undertow</artifactId> <version>2.1</version> </dependency> <dependency> <groupId>com.jfinal</groupId> <artifactId>jfinal</artifactId> <version>4.9</version> </dependency>
I will not post the full code (after all, it is too long). The code will be submitted to the code warehouse, and interested students can access the code warehouse.
In fact, I'm used to the process of creating a project with spring boot, and I'm not used to building a project in this way. I can exclude the IDEA's support for building a spring boot project and directly access it https://start.spring.io/ Check the box to download and import the required dependencies directly into the IDE.
However, there's nothing to say about this. After all, spring boot is supported by a large team, while JFinal seems to have only one developer. It's basically the pride of the Chinese people in the open source field.
Project launch
The project dependency is well done. The first thing to do next is to find a way to start the project. In JFinal, there is a global configuration class, and the code of the startup project is also here.
This class needs to inherit JFinalConfig. To inherit this class, you need to implement the following six abstract methods:
public class DemoConfig extends JFinalConfig { public void configConstant(Constants me) {} public void configRoute(Routes me) {} public void configEngine(Engine me) {} public void configPlugin(Plugins me) {} public void configInterceptor(Interceptors me) {} public void configHandler(Handlers me) {} }
configConstant
This method is mainly used to configure some constant values of JFinal, such as setting cglib for aop agent, slf4j for log system, UTF-8 for default encoding format, etc.
Here are some configurations from the official documents I selected:
public void configConstant(Constants me) { // Configure the development mode. The value of true is the development mode me.setDevMode(true); // Configure the aop agent to use cglib, otherwise the default dynamic compilation agent scheme of jfinal will be used me.setToCglibProxyFactory(); // Configure dependency injection me.setInjectDependency(true); // Whether to inject the superclass of the injected class when configuring dependency injection me.setInjectSuperClass(false); // Configure as slf4j log system, otherwise log4j will be used by default // You can also use me.setLogFactory(...) configured as self expanding log system implementation class me.setToSlf4jLogFactory(); // Set the Json conversion factory implementation class. See the twelfth chapter for more details me.setJsonFactory(new MixedJsonFactory()); // Configure the view type, and use the jfinal enjoy template engine by default me.setViewType(ViewType.JFINAL_TEMPLATE); // Configure 404, 500 pages me.setError404View("/common/404.html"); me.setError500View("/common/500.html"); // Configure encoding, default to UTF8 me.setEncoding("UTF8"); // Configure the data parttern used by json to convert the Date type me.setJsonDatePattern("yyyy-MM-dd HH:mm"); // To configure whether access to JSP is denied or not is to directly access. JSP files, and renderjsp( xxx.jsp )Nothing me.setDenyAccessJsp(true); // Configure the maximum data amount of uploaded file, default 10M me.setMaxPostSize(10 * 1024 * 1024); // Configure the urlPara parameter separator character, which is "-" by default me.setUrlParaSeparator("-"); }
This is the general configuration information of some projects. In spring boot, this configuration information is usually written in yaml or property configuration file. However, it doesn't matter to configure this way, but it's a little bit uncomfortable.
configRoute
This method is to configure access route information. My example is as follows:
public void configRoute(Routes me) { me.add("/user", UserController.class); }
Seeing this, I think of a problem. Every time I add a Controller, I have to come here to configure the routing information. It's silly.
If it's a small project, it's OK. There's not a lot of routing information. There are a dozen or dozens of them that are enough. If it's a few medium and large projects, hundreds or thousands of controllers, if I'm all configured here, can I find them? Here's a question mark.
There is a fatal problem in the practical application. When the version is released, students who have done the project know that there are at least four environments: development, testing, UAT and production. The code function version of each environment is different. Do I need to manually modify here before publishing? How can I manage it.
configEngine
This is used to configure the Template Engine, that is, the page template. I just want to write two Restful interfaces simply. Here I will not do the configuration. Here is an official example:
public void configEngine(Engine me) { me.addSharedFunction("/view/common/layout.html"); me.addSharedFunction("/view/common/paginate.html"); me.addSharedFunction("/view/admin/common/layout.html"); }
configPlugin
Here is the Plugin used to configure JFinal, that is, some plug-in information. My code is as follows:
public void configPlugin(Plugins me) { DruidPlugin dp = new DruidPlugin(p.get("jdbcUrl"), p.get("user"), p.get("password").trim()); me.add(dp); ActiveRecordPlugin arp = new ActiveRecordPlugin(dp); arp.addMapping("user", User.class); me.add(arp); }
My configuration is very simple. I configured Druid's database connection pool plug-in in the front and ActiveRecord database access plug-in in the back.
What makes me feel a little silly is that if I want to increase the mapping relationship of ActiveRecord database access, I need to manually add code here, such as arp.addMapping ("aaa", Aaa.class );, or go back to the above problem. The publishing system between different environments needs to be manually modified here. If the project is not large, it can be managed manually. If the project is large, it will become a nightmare.
configInterceptor
This method is used to configure global interceptors, which are divided into two types: control layer and business layer. My example code is as follows:
public void configInterceptor(Interceptors me) { me.add(new AuthInterceptor()); me.addGlobalActionInterceptor(new ActionInterceptor()); me.addGlobalServiceInterceptor(new ServiceInterceptor()); }
here me.add(...) and me.addGlobalActionInterceptor(...) the two methods are completely equivalent. They are interceptors configured to intercept the action methods in all controllers. And me.addGlobalServiceInterceptor(...) configured interceptors will intercept all public methods in the business tier.
There's nothing to say about interceptors, so the configuration feels exactly the same as that in spring boot.
configHandler
This method is used to configure JFinal's Handler, which can take over all Web requests and have full control over the application.
This method is a high-level extension method. I just want to write a simple CRUD operation, which I don't need at all. Here is an official Demo:
public void configHandler(Handlers me) { me.add(new ResourceHandler()); }
configuration file
As like as two peas, I think the official configuration file ends up with txt, which makes me start to doubt life at the very first time. Why does the configuration file need to be in TXT format, and the configuration format is exactly the same as the property file. Is it to show individuality? This gives me deep doubt.
In the previous DemoConfig configuration class, you can get the content of the configuration file directly through Prop:
static Prop p; /** * PropKit.useFirstFound(...) Use the configuration file found first from left to right in the parameter * Look for the configuration from left to right. If it is found, it will be loaded and returned immediately. Subsequent configurations will be ignored */ static void loadConfig() { if (p == null) { p = PropKit.useFirstFound("demo-config-pro.txt", "demo-config-dev.txt"); } }
Although the concept of environment configuration is introduced in the configuration file, it is still a bit rough. Many of the content that needs to be configured cannot be configured, but only the limited content such as database and cache service can be configured for the time being.
Model configuration
To be honest, I was shocked when I first saw the use of this part of Model. I didn't expect it to be so simple at all:
public class User extends Model<User> { }
In this way, it's OK. I don't need to write anything in it, which completely subverts my previous cognition. Isn't this framework going to search for fields in the database dynamically? It's not an intelligent problem. If two people develop the same project together, I don't know the Model just by looking at the code What are the properties in it? You have to look at the database together. It will crash.
Later, it turned out that I was young, and the code was still needed, but I didn't have to write it myself. JFinal provided a code generator. If the relevant code was generated automatically according to the database table, the generated code would not be looked at. Simply look at the code of this automatic generator:
public static void main(String[] args) { // Package name used by base model String baseModelPackageName = "com.geekdigging.demo.model.base"; // base model file save path String baseModelOutputDir = PathKit.getWebRootPath() + "/src/main/java/com/geekdigging/demo/model/base"; // Package name used by model (the package name used by MappingKit by default) String modelPackageName = "com.geekdigging.demo.model"; // model file save path (MappingKit and DataDictionary file default save path) String modelOutputDir = baseModelOutputDir + "/.."; // Create generator Generator generator = new Generator(getDataSource(), baseModelPackageName, baseModelOutputDir, modelPackageName, modelOutputDir); // Configure whether to generate comments generator.setGenerateRemarks(true); // Set database dialect generator.setDialect(new MysqlDialect()); // Set whether to generate chain setter method generator.setGenerateChainSetter(false); // Add table names that do not need to be generated generator.addExcludedTable("adv", "data", "rate", "douban2019"); // Set whether to generate dao objects in the Model generator.setGenerateDaoInModel(false); // Set whether to generate dictionary files generator.setGenerateDataDictionary(false); // Set the prefix of the table name that needs to be removed to generate the modelName. For example, table name "osc"_ User ", remove prefix" osc "_ The resulting model is named "user" instead of OscUser generator.setRemovedTableNamePrefixes("t_"); // generate generator.generate(); }
My heart is cold when I see this code. I actually scan the whole database. Fortunately, MySQL is used. It's open source and free. If it's Oracle, a project needs a database or a database cluster, which is too rich.
Of course, this code also provides the addExcludedTable() method to exclude the table name that does not need to be generated. In fact, it has no use value. There may be more than N projects running together on an Oracle cluster. There are hundreds of tables above. If a small project only uses more than ten tables, the addExcludedTable() method just copies the table name and estimates that it will not be completed in a day or two.
Database CRUD operation
JFinal integrates CRUD operation of data into the Model. How can I not evaluate this method? Take a look at a sample Service class I wrote:
public class UserService { private static final User dao = new User().dao(); // Paging query public Page<User> userPage() { return dao.paginate(1, 10, "select *", "from user where age > ?", 18); } public User findById(String id) { System.out.println(">>>>>>>>>>>>>>>>UserService.findById()>>>>>>>>>>>>>>>>>>>>>>>>>"); return dao.findById(id); } public void save(User user) { System.out.println(">>>>>>>>>>>>>>>>UserService.save()>>>>>>>>>>>>>>>>>>>>>>>>>"); user.save(); } public void update(User user) { System.out.println(">>>>>>>>>>>>>>>>UserService.update()>>>>>>>>>>>>>>>>>>>>>>>>>"); user.update(); } public void deleteById(String id) { System.out.println(">>>>>>>>>>>>>>>>UserService.deleteById()>>>>>>>>>>>>>>>>>>>>>>>>>"); dao.deleteById(id); } }
I'm a bit confused about the paging query here. Why does a SQL have to be split in two? I always feel that the latter half is from user where age >? It's HQL of Hibernate. Is there any secret between the two.
Other common CRUD operations are quite normal without any slot points.
Controller
Let's start with the code, just the code nagging:
public class UserController extends Controller { @Inject UserService service; public void findById() { renderJson(service.findById("1")); } public void save() { User user = new User(); user.set("id", "2"); user.set("create_date", new Date()); user.set("name", "Small red"); user.set("age", 24); service.save(user); renderNull(); } public void update() { User user = new User(); user.set("id", "2"); user.set("create_date", new Date()); user.set("name", "Small red"); user.set("age", 19); service.update(user); renderNull(); } public void deleteById() { service.deleteById(getPara("id")); renderNull(); } }
First, the Service uses @ Inject for injection, which is nothing to say, just like @ Autowaire in Spring.
The return type of all the actual methods in this class is void, and the returned content is controlled by render(). You can return json or page view. Well, it's just a little bit unsuitable. There's no problem.
But then this problem makes me a little bit square. It's not a problem. It's a defect. There are only two ways to get parameters:
One is the getPara() series of methods, which can only get the data submitted by the form, basically similar to the Spring request.getParameter() .
The other is getModel / getBean. First, these two methods accept the parameters submitted through the form. Second, they must be converted to a Model class.
I just want to know one thing. If the type of a request is not form submission, but application/json, how to accept parameters, I have turned over the document several times, and I haven't found the request object I want.
Maybe it's just that I haven't found a mature framework that doesn't support this common application/json data submission method, which is impossible.
In addition, getModel / getBean must be directly converted into a Model class. Sometimes it's not a good thing. If the input parameter format of the current interface is complex, it's still difficult to construct this Model. Especially, sometimes it's only necessary to obtain a small amount of data in it for analysis and preprocessing, so it's not necessary to parse the entire request data.
Summary
Through a simple CRUD operation, JFinal as a whole has completed what a WEB + ORM framework should have, but in some places, it is not so good. Of course, it is compared with spring boot.
If it's used to make some small things, it's still worth trying. If it's to do some enterprise level applications, it's a bit of a stretch.
However, this project came out earlier. It has been eight years since 2012. If it is compared with the framework of spring MCV + Spring + orm in that year, I think I will definitely choose JFinal if I choose it.
Compared with the current spring boot, I think I prefer to choose spring boot, one is because I am familiar with it, the other is because of JFinal In many places, in order to facilitate the use of developers, quite a lot of code has been encapsulated. This approach can't be bad. It's certainly good for beginners. Simply look at the documents, and you can start to work in half a day or even a day. But for some old drivers, it will make people feel tied up, which can't be done or that can't be done.
My own sample code and official Demo have been submitted to the code warehouse together. Students who need it can reply "JFinal" to get them.