Buliding With WebWork2

Introduction

Other Articles in the Framework Series
Introduction to Maverick
Introducing the Spring Framework
Keel: The Next Generation Meta-Framework

WebWork is a Model 2 MVC web framework created by the OpenSymphony team which includes folks like Jason Carreira, Pat Lightbody, Mike Cannon-Brookes, Hani Suleiman and many more. Having already gained considerable reputation with their current 1.x release the team broke out and has been working on a 2.x version which already has a beta release out.

This article will take you through the development of the wafer weblog application using the WebWork 2.x release and will cover many of the basic features of WW2. Last time I wrote for TheServerSide it was about how to build the Wafer Weblog with the Maverick web framework. I choose Maverick because it was considered "lighter than Struts" and after building wafer I agreed and was very pleased. This time I chose WebWork2 mainly to learn about all the hype it was getting. I was curious to know what’s all the hype with IoC, Interceptors, etc. And this time I was even more impressed.


The executive summary of WebWork2

WebWork2 is a Model 2 MVC web framework that leverages the cutting edge solution strategies of IoC, Interceptors and the OGNL expression language. It is built with interfaces instead of abstract classes, allowing you to implement your solution loosely coupled to the framework yet strongly leveraging it. Its independence from the J2EE package allows your solutions to integrate smoothly into backend solutions as never before possible.


What’s different in the WebWork2 beta release?

Quite a bit actually. The core MVC component has been pulled out of WebWork and moved into a separate project called Xwork, now coined as a generic command pattern framework independent of the web. They also added Interceptors which allow you to take all of that pre and post processing you were doing in Action class hierarchies and pull them out into separate classes independent of Actions; however, you still have access to them and their environment. UI validation is now done via an XML file instead of in the Action class; however, that feature still exists if you need it. And they’ve added the OGNL expression language and IoC.


Getting Started

Let’s start by setting up your project. As easy as this sounds it’s sometimes not that simple. Knowing which files you need, the correct directory structure to use and what Ant modifications will be required are always a challenge when starting with a new framework. Unfortunately WW2 doesn’t provide much help with this but neither do other frameworks so the easiest way to get moving is to create a J2EE structure much like I did for the wafer weblog application. At the time of writing this article a neat tool called megg was released to do just that, create a WW2 project with almost everything you need to get started, like a complete project directory structure for src, config, jsp, html, lib and more. It even goes as far as creating a simple HelloWorld app with a Junit test and has a fully functional Ant build file already tailored to your web application’s name!


Configuration

The first place to start for those familiar with Struts would be the configuration piece. Like many other frameworks WW2 has an xml file to control the flow of actions and outputs. Below is a portion of the wafer weblog xwork.xml:

<action name="RegisterAction" class="com.flyingbuttress.wafer.actions.RegisterAction"> <result name="input" type="dispatcher"> <param name="location">/register.jsp</param> </result> <result name="success" type="chain"> <param name="actionName">LoginAction</param> </result> <interceptor-ref name="defaultStack"/> <interceptor-ref name="validation"/> <interceptor-ref name="workflow"/> </action> 

The format is fairly clear. The action name is the reference name for that action and is most likely the name you will also use in your URL/HTML like /HelloWorld.do. The result defines what type of view this action should forward on to and it has a name and a type. The name (input, success, error, etc.) needs to map to your Action return value and the type field defines what type of View this action is going to, see below for the current list of result types.

  • Dispatcher - JSP routing
  • Redirect - forward redirecting
  • Velocity - for a velocity templating view
  • Chain - for routing to another Action class

If you need to create a unique result type such as Excel output you can easily create your own result type by implementing the Result interface, adding the implementation code and adding the result to the list of possible result types in the webwork-default.xml file.


Interceptors

The next logical piece to discuss are the Action classes; however, it’s almost impossible to continue without explaining the concept of Interceptors. Most of all the work that is done in WW2 is done via interceptors. Personally I find the implementation of interceptors to be one of the core unique factors in what separates WW2 from the rest of the frameworks out there. On the surface interceptors are much like filters. Others have compared them to "Practical AOP". Regardless of the name, the feature is very nice.

Interceptors can be called before the action is invoked, after the action or both before and after. They have full access to the Action class and the environment at runtime allowing you to either call methods on the Action class or work with the environment of the Action class. A great example of both would be the TimerInterceptor. Before the action is invoked it takes a timestamp, then, after the completion of Action it gets another timestamp and calculates the length of time it took to run. Poor man’s profiling!

Another joy of the interceptors is the ease at which you can configure the interceptor stack. Every <action> in the xwork.xml can have one or many <interceptor-ref> tags associated to that action. A best practice is to reference a stack of interceptors, because you might want to call more then a few interceptors much like how "defaultStack" is referenced in the wafer weblog application. BEWARE: The ordering of the stack is very important; they are called in the order which they are defined so if one interceptor relies on another to have already been called then you better have it above that interceptor in the stack! For a complete list of interceptors see webwork-default.xml.



Struts has Interceptors too, sort of, as a Struts add-on, http://struts.sourceforge.net/saif/index.html; however, many consider this to be a proof of concept. In general, it seems that it provides similar functionality but it has a way to go before it is ready for mass consumption and maybe longer until it gets integrated with the main Struts build.


Create your own Interceptor

Enough talk, let’s create one! All Interceptors must implement the Interceptor interface which basically has 3 methods, init, destroy and intercept. For my needs I extended the AroundInterceptor which allows me to call a before and after method. Let’s create an Interceptor that requires the user to be logged in to execute an Action. It will do this by checking to see if the user object is in the session before the Action is executed and if the object is not found the user is sent to the login.

public class AuthorizeInterceptor extends AroundInterceptor { private static final Log log = LogFactory.getLog(LoggingInterceptor.class); private boolean loggedIn = false; protected void before(ActionInvocation invocation) throws Exception { User u = null; ActionContext ctx = ActionContext.getContext(); Map session = ctx.getSession(); u = (User)session.get("user"); if(u == null) { log.info("User not logged in"); loggedIn = false; } else loggedIn = true; } protected void after(ActionInvocation invocation, String result) throws Exception { log.info("After"); } public String intercept(ActionInvocation invocation) throws Exception { before(invocation); if(loggedIn == false) return Action.LOGIN; // send em back to the login screen else { String result = invocation.invoke(); after(invocation, result); return result; } } } 

The before method is called before the execute is invoked allowing me to check if the user has logged onto this session and if not then I return the global value to log in, "login", which is define in the <global-results> in the xwork.xml file. Next you have to add an interceptor definition in your xwork.xml file.


<interceptors> <!-- custom created Interceptor for checking if a user has already logged --> <interceptor name="login" class="com.flyingbuttress.wafer.interceptor.AuthorizeInterceptor"/> </interceptors> 

Now you have an AuthorizationInterceptor. For all of your action classes that you want to hide behind a login just reference this interceptor in your action config, as shown below, and you now have basic Action security.


<action name="ShowCommentsAction" class="com.flyingbuttress.wafer.actions.ShowCommentsAction"> <result name="success" type="dispatcher"> <param name="location">/comments.jsp</param> </result> <interceptor-ref name="login"/> <interceptor-ref name="defaultStack"/> </action> 

You can also create an interceptor that calls a method in your action class either before or after the execute statement much like how the "workflow" interceptor calls validate() on the Action class before it is executed. This concept could be applied to say calling an init() method in all of your Action classes before the execute method is called.


Pitfalls to Interceptors

I love interceptors but it was also one of the hardest pieces to operate successfully right out of the gate. The problem is that some interceptors have dependencies on other interceptors to work so making sure that you have the correct interceptors assigned to your action class AND that they are in the correct order is crucial. For the sake of simplicity I only referenced the one standard interceptor stack called defaultStack. All other interceptors I referenced by name in the <action/> call. For the more weathered WebWork veteran this is a verbose way to call interceptors but I found that it helped greatly in keeping everything very clear.


Action Classes

Actions, Controllers, Commands are pieces the developers deal with the most. In the WebWork world they are called Actions and there are basically two types of them: Field Driven and Model Driven. Think of the Field Driven Actions as Controller-as-Model style; these are probably the best choice for one-off pages with very small models. Most of the wafer web log application was done using this model.

The other type is Model Driven. This is where the model is its own POJO. This style is better for larger models and promotes better code reuse. To define an Action class, regardless of the above mentioned types, you have to either extend ActionSupport or implement Action. In the development of my app I chose extension of ActionSupport because of all the helper features it has like UI error handling and logging. Let’s start with a Field Driven Action, RegisterAction.java

public class RegisterAction extends ActionSupport { String username, email, firstname, lastname, password; private User tempUser; public String getUserName() { return username; } public void setUserName(String username) { this.username = username; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getFirstName() { return firstname; } public void setFirstName(String firstname) { this.firstname = firstname; } public String getLastName() { return lastname; } public void setLastName(String lastname) { this.lastname = lastname; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String execute() throws Exception { if (hasErrors()) return INPUT; else { tempUser = WebLogSystem.getUserStore().create(this.username,this.password ); tempUser.setFirstName(this.getFirstName()); tempUser.setLastName(this.getLastName()); tempUser.setEmail(this.getEmail()); tempUser.save(); return SUCCESS; } } /** * Do business logic validation by checking to see if the user entered is * already in the database. */ public void validate() { LOG.info("Validating the registration page"); try{ if(WebLogSystem.getUserStore().verify(this.username)) { this.addFieldError("Username", "Someone already has that name"); } } catch(Exception e) {e.printStackTrace(); } } } 

The one method you must implement is execute(). Execute is called every time your action is invoked and it has a String return value. There are default values defined in the Action Interface which are success, input, none and error and these values map directly to the result name fields in the xwork.xml file.


Validation

WW2 has both UI and data validation. UI validation basically checks to see that your value types and ranges are correct for a field. For example, a number field is really a number or a date is really a valid date, etc. Data validation is when you need to check if the value given is valid from say a list of possible choices, maybe requiring a database lookup. This type of validation would be used on say US zip code checking where checking that a number simply falls within a given range isn’t enough, and you need to check a list of valid zip codes to see if it is really valid.


User Interface validation

This type of validation is done in an XML file which you define in the same package as the Action class by calling it <ActionClassName>-validation.xml. Below is an example for the RegisterAction-validation.xml

<validators> <field name="userName"> <field-validator type="requiredstring"> <message>You must enter a value for username</message> </field-validator> </field> <field name="email"> <field-validator type="requiredstring"> <message>You must enter a value for email</message> </field-validator> </field> <field name="email"> <field-validator type="email"> <message>You must enter a valid email</message> </field-validator> </field> <field name="firstName"> <field-validator type="requiredstring"> <message>You must enter a value for first name</message> </field-validator> </field> <field name="lastName"> <field-validator type="requiredstring"> <message>You must enter a value for last name</message> </field-validator> </field> <field name="password"> <field-validator type="requiredstring"> <message>You must enter a value for password</message> </field-validator> </field> </validators> 

Below is the resulting screen shot of a failed UI validation:




WW2 comes with many field validators like date, email, int, string, etc. and all valid Validators are defined in the validator.xml file; however, if you have a unique need, it is very easy to create your own field validator by implementing the validator interface and adding the reference to the validator.xml file. UI validation is called with the "validation" interceptor, so to turn on validation you also must reference the "validation" interceptor in your action configuration much like the RegisterAction has in the xwork.xml for wafer weblog.


Data Validation

Data validation most likely involves you the programmer to write some code to check a scenario like "Is this a valid zip code, let me query the database." To do this create a no args method in your Action class called validate and put all of your functionality there. Then implement Validatable, referencing the "workflow" interceptor. This interceptor will call the validate method in the Action class before the execute is called, allowing you to add any errors to the context as needed.


Inversion of Control

IoC is a design pattern that favors loose coupling between classes/components. Currently, when you code you have some classes that depend on others to operate and those dependencies are coupled closely with the class. So why is IoC interesting? It promotes good design by separating classes via interface and implementation and it allows the container to manage the lifecycle of your components.

The best way to describe it is with an example. Let’s say your company creates scales for humans and aliens to use to weigh themselves and that these scales will be sold on Earth, Venus and Mars. The problem is that gravity is different on these different planets, so in order to ensure that they know their true weights in terms of Earth pounds the scales will have to be flexible to handle that need. The ingredients needed for making IoC work are the following:

  • components.xml (IoC config file)
  • Scale.java (Interface for all the components)
  • ScaleAware.java (Interface for Action class)
  • MarsScaleImpl.java (component)
  • VenusScaleImpl.java (component)
  • EarthScaleImpl.java (component)
  • ScaleAction.java (Action class)

Lets’ take a look at component.xml:

<components> <component> <scope>application</scope> <class>com.flyingbuttress.scale.MarsScale</class> <enabler>com.flyingbuttress.scale.ScaleAware</enabler> </component> </components> 

Here I define the scope of the component, the implementation class and the interface that notifies the container that any Action class implementing this interface has a dependency to the above class. Lets look at how this is done in the Action class:

public class ScaleAction implements Action, ScaleAware { private Scale scale; public void setScale(Scale scale) { this.scale = scale; } public String execute() throws Exception { System.out.println("The weight of you is:" + scale.getWeight()); return SUCCESS; } } 

Now the container sees that this implements ScaleAware; therefore, it will call setScale and pass in the implementing class via the interface. Now for all those scales sold on Mars, all that needs to be done is to have the class definition in the components.xml set to MarsScale and those on Earth set to EarthScale. There are many different reasons to do this, besides interstellar commerce, but either way the implmentation is the same. If given the opportunity the entire user management of the wafer weblog application could be done using IoC.

The IoC capability is interesting but be careful not to treat it as a solution looking for a problem. IoC doesn’t work everywhere so use it appropriately, otherwise it might go the way Frames did.


Working with the JSP View


The Tags

The most common way most frameworks send and receive information on a JSP page is via a tag library. Some use JSTL while others like Webwork have their own set of tags. For a complete listing see the WebWork Wiki. Most of the JSP’s in the wafer application utilized the WW tags which I found to be very useful and easy to use. Here is a simple usage of the tags:

<ww:property value="user.firstName" /> 

or

<ww:textfield label="First Name" name="firstName" ></ww:textfield> 

The first example calls getUser() in the Action that called this page, then it calls getFirstName() on that object. The second example will create an input box with a label of ‘First Name’ with name for the input box of firstName. While it appears that this tag doesn’t do much (anyone can create a simple HTML input tag) it does handle inline error messaging which I find very nice (see screen shot above).


JSTL

If you’re a "standards" kind of person then you can use JSTL. The below tag will do the same as the WW2 tag above of <ww:property value="user.firstName" />

<c:out value=${user.firstName}/>


Ognl and the OgnlValueStack

Ognl (Object Graphical Navigation Language) is much like JSTL except, unlike JSTL which is primarily used for getting things out, Ognl can be used to set things as well. With Ognl you can create a Map on the fly like this.

<ww:select label="’Gender’" name="’gender’" list="#{‘true’ : ‘Male’, ‘false’ : ‘Female’}"/> 

You can also pull values from the ActionContext like this:

<ww:property value="#name" /> 

where name was set in an Action class like this:

ActionContext ctx = ActionContext.getContext(); ctx.put("name", otherUser.getUsername()); 

Ognl also takes care of trivial and complete type conversion for you. For example, you can pass in from say an input text box the value of "10/14/1971" and it converts it into a Date object for you using a setDate accessor method most likely in your Action class. If you have the need or desire you can also create your own type converters for your custom objects.

Lastly, the power Ognl provides Xwork is in the OgnlValueStack, which is basically a stack for storing request scoped values. If used with the parameterized interceptor you can place all of the parameters from a form on the stack for later retrieval in the code. This is another feature that acts like a J2EE component (HttpRequest) but it’s not the same, which distinguishes it from the Servlet API. One nice way to use the OgnlValueStack is by simplifying a large Controller-as-model Action class. Lets say you have an Action class that maps to a form that has 30 parameters. That means if you are using the Controller-as-Model pattern you will have at least 30 set accessor methods to store the parameters from the submission in your class. But with the OgnlValueStack you can now simply have calls like:

String bla = (String) stack.findValue("bla"); 

This is perhaps not as clean as accessor methods but its different strokes for different folks.


Other Features not mentioned in detail


Action packaging

This allows you to jar up a set of Action classes and include the xwork.xml file as an include in the master xwork.xml file. You can also do the same for Velocity views, which, if used together will allow you to componentize your application for easier sharing of pieces of functionality.


UI Components and Custom components

Webwork allows you to create reusable, skinnable user interface components like a calendar date picker that so many weblogs seem to have these days.


Namespaces and Packages

You can bundle up configurations in xwork.xml into packages which can be used to extend other packages, gaining access to all actions, interceptors, etc. Add namespaces with the packaging and you can create action aliases with different classes giving you the ability to have RegisterAction.action in one namespace point to a different class in another namespace.


Why do I like WebWork2?

You may remember me ranting about how great Maverick is a few months back on TSS. So how does WebWork2 compare you ask? It’s very similar. Both are built with a lot of interfaces to help promote extensibility and decoupling. Both are easy to use and have a well designed code base. But the big difference is that WebWork has Interceptors and IoC. You can handle much of the pre and post processing in Interceptors instead of in Action hierarchies like I did in the Wafer weblog application using Maverick when I was trying to do authentication. What about Struts? Struts does have much of the functionality that WW2 has BUT it is not part of the base release. For many that may not be a big deal but when it comes time for company standards those add-ons most likely won’t be allowed. Also, Struts has full abstract classes and few interfaces.

WebWork is a Model-2 MVC framework. How many other frameworks fit that bill? Here is a possible list of reasons why you might be interested in using WW2:

  • It’s built with interfaces instead of concrete classes
  • You like some of the features not included in the "other" framework like IoC or Interceptors.
  • You are looking for an MVC that is not bound to the J2EE environment, making unit testing easier along with other tasks.
  • Many companies are moving from Struts to Webwork as their standard.
  • You drink Pepsi instead of Coke.

Conclusion

Building the Wafer weblog application was truly a dream with WebWork. Their use of interfaces and interceptors make it easy and flexible and make it difficult to not day dream of all the ways I could work with these new concepts. The community is big and strong with leaders one can be proud of. In my Maverick article I mentioned that if you are going to learn one framework maybe you should choose Struts over Maverick. This time, I would say choose WebWork.


About the Author

Kris Thompson is the lead of a local Java user group, www.frameworks-boulder.org in Boulder Colorado that focuses solely on web frameworks and is also a contributor on the Expresso framework and Wafer project and author of other framework articles. You can email Kris at info@flyingbuttressconsulting.com.


Resources

Mike Cannon’s Talk at TSS, http://wiki.opensymphony.com/space/Writeup+of+Mike%27s+Talk+at+TSS+on+WebWork2

The Opensysmphony wiki, http://wiki.opensymphony.com/space/WebWork2

Enigmastation.com, http://www.enigmastation.com/technology/webwork/competency.html

PicoContainer, http://www.picocontainer.org/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于Django+python编写开发的毕业生就业管理系统支持学生教师角色+db数据库(毕业设计新项目).zip 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用!有问题请及时沟通交流。 2、适用人群:计算机相关专业(如计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网、自动化、电子信息等)在校学生、专业老师或者企业员工下载使用。 3、用途:项目具有较高的学习借鉴价值,不仅适用于小白学习入门进阶。也可作为毕设项目、课程设计、大作业、初期项目立项演示等。 4、如果基础还行,或热爱钻研,亦可在此项目代码基础上进行修改添加,实现其他不同功能。 欢迎下载!欢迎交流学习!不清楚的可以私信问我! 基于Django+python编写开发的毕业生就业管理系统支持学生教师角色+db数据库(毕业设计新项目).zip基于Django+python编写开发的毕业生就业管理系统支持学生教师角色+db数据库(毕业设计新项目).zip基于Django+python编写开发的毕业生就业管理系统支持学生教师角色+db数据库(毕业设计新项目).zip基于Django+python编写开发的毕业生就业管理系统支持学生教师角色+db数据库(毕业设计新项目).zip基于Django+python编写开发的毕业生就业管理系统支持学生教师角色+db数据库(毕业设计新项目).zip基于Django+python编写开发的毕业生就业管理系统支持学生教师角色+db数据库(毕业设计新项目).zip基于Django+python编写开发的毕业生就业管理系统支持学生教师角色+db数据库(毕业设计新项目).zip基于Django+python编写开发的毕业生就业管理系统支持学生教师角色+db数据库(毕业设计新项目).zip基于Django+python编写开发的毕业生就业管理系统支持学生教师角色+db数据库(毕业设计新项目).zip
毕设新项目基于python3.7+django+sqlite开发的学生就业管理系统源码+使用说明(含vue前端源码).zip 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用!有问题请及时沟通交流。 2、适用人群:计算机相关专业(如计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网、自动化、电子信息等)在校学生、专业老师或者企业员工下载使用。 3、用途:项目具有较高的学习借鉴价值,不仅适用于小白学习入门进阶。也可作为毕设项目、课程设计、大作业、初期项目立项演示等。 4、如果基础还行,或热爱钻研,亦可在此项目代码基础上进行修改添加,实现其他不同功能。 欢迎下载!欢迎交流学习!不清楚的可以私信问我! 学生就业管理系统(前端) ## 项目开发环境 - IDE: vscode - node版本: v12.14.1 - npm版本: 6.13.4 - vue版本: @vue/cli 4.1.2 - 操作系统: UOS 20 ## 1.进入项目目录安装依赖 ``` npm install ``` ## 2.命令行执行进入UI界面进行项目管理 ``` vue ui ``` ## 3.编译发布包(请注意编译后存储路径) #### PS:需要将编译后的包复制到后端项目的根目录下并命名为'static' 学生就业管理系统(后端) ## 1.项目开发环境 - IDE: vscode - Django版本: 3.0.3 - Python版本: python3.7.3 - 数据库 : sqlite3(测试专用) - 操作系统 : UOS 20 ## 2.csdn下载本项目并生成/安装依赖 ``` pip freeze > requirements.txt pip install -r requirements.txt ``` ## 3.项目MySQL数据库链接错误 [点击查看解决方法](https://www.cnblogs.com/izbw/p/11279237.html)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值