Struts2, Spring, Hibernate best practices for efficient development

The introduction

SSH (Struts2+Spring+Hibernate) is the most well-known Java EE Web component layer development technology in the Java industry. Many people refer to Java EE and even mistake it for SSH. Most of the books and electronic tutorials are the same, explaining how to use various tags and configurations. Many people, including this author, followed the tutorial when they first started using SSH. Tedious configuration, repeated configuration modification, constant definition of the parameter converter, really let the author unbearable. This paper tries to redefine the development mode of SSH. According to the principle that protocol is better than configuration, a new SSH development framework is designed by using Java reflection, annotations and other technologies. The application of this framework can greatly improve the development efficiency. It works every time.

Prior to reading this article, you should have some understanding of SSH combined development, preferably with practical experience, especially with Struts2, Struts2 custom interceptor, custom validator, etc. In addition, the reader needs to master some prerequisite techniques, including Java reflection, Java annotations, and understanding transaction isolation levels.

The article firstly introduces the overall framework, and then introduces the detailed design of each part. In addition, the article will enumerate the technical features adopted in the framework and their corresponding purposes, hoping that readers can benefit from it.

Overview of the Framework

The framework is said to be generic because its thinking fits any business requirement. According to the article, after the code architecture is built according to the framework introduced, many technical details can be shielded and developers can focus on the implementation of business logic. These tedious technical details include technical configuration, permission control, page jump, error handling and so on. The general style of the framework is shown in Figure 1. In general, the framework adheres to the principle of “specification over configuration.”

Figure 1. General style of the framework (To view a larger version)

In the figure above, the system has only one Action configuration. Each business operation no longer corresponds to an ActionSupport subclass, but corresponds to a class method of ActionSupport subclass. The dynamic method feature of Struts2 makes the business method get rid of the tedious configuration, and it is convenient to add and delete. Comprehensive result configuration solves the problem of various page jumps. Action method name, comply with the permission and business model protocol, let model selection and permission control to the framework to achieve, and access the operation name is [method name].action. Next, the pieces of a pair of frames are explained.

Struts2 static configuration

In this framework, we recommend defining only a small number of classes to inherit from ActionSupport, so that the struts.xml configuration can be minimal or even minimal, without changing the configuration as the business grows. Listing 1 is the Action configuration defined by the author for a teacher attendance system.

Listing 1. Struts2 configuration checklist
<action name="*" method="{1}" class="Main"> 
    <interceptor-ref name="fileUpload"> 
        <param name="maximumSize">4073741824</param> 
    </interceptor-ref> 
    <interceptor-ref name="myInterceptorStack"></interceptor-ref> 
    <result name="input">/noDir/error.jsp</result> 
    <result type="json" name="success"></result> 
    <result name="errorJson" type="json"></result> 
    <result type="json" name="error"></result> 
    <result type="stream" name="stream"> 
        <param name="contentType">${contentType}</param> 
        <param name="contentDisposition">fileName="${inputFileName}"</param> 
        <param name="inputName">inputStream</param>  
    </result> 
    <result name="dynamic">/${url}</result> 
    <result name="otherAction" type="redirectAction">/${url}</result> 
    <result name="red" type="redirect">/${url}</result> 
</action>Copy the code

Listing 1 defines many rules. First of all, this configuration uses dynamic method call technology, which enables many Action methods can be declared in a class, do not have to define the Action class repeatedly, at the same time, the addition and deletion of business can be simplified to the Action class method addition and deletion, the addition of Action methods do not need to carry out other configuration. If the business is deleted, you just need to comment or delete the method, which is very convenient. Second, the configured package inherits from JSON-default, which means that the action in this package supports Ajax calls. By default, if ERROR or SUCCESS is returned, the action will be serialized as JSON and returned to the client. We have defined various jump types, including redirection to page (specified by the URL in the Action), redirection to Action, redirection to error page, return of stream type, and so on, which make it easy to dynamically select return result data. So what are the development benefits of using the above configuration? Assuming we have a new business, we just need to add a new method to the Action, as shown in Listing 2.

Listing 2. Adding a new business method
Public String business() throws Exception {... Business process... If (has error) {addFieldError(" error message ") return INPUT; } else {//url is a String variable defined in the action, specifying the jump address url = "business.jsp"; Return "dynamic"; }}Copy the code

To invoke the new business method, just call the link: http://{host}:{port}/{webapp}/business.action. As we can see, with the new business logic method defined, we did not modify or add any configuration because our configuration was complete, allowing for various jumps, error cases, and dynamic method invocation features that allowed us to specify business methods dynamically. Listing 2 is even simpler if we extract the jump code in Listing 2. See Listing 3.

Listing 3. Business method after extracting the base method
Public String business() throws Exception {... Business process... if (has error) { return redirectToErrorPage("error message") } else { return redirectToPage("business.jsp"); }}Copy the code

In the listing above, we have extracted the redirectToErrorPage and redirectToPage methods so that other business methods can reuse these jump methods and make the business process clear and understandable. Similarly we can extract redirectToAction (jump to another business method), redirectStream (stream-type jump), redirectToAnotherPage (jump for redirection), and redirectToJson (Ajax) Jump) and so on. These public methods can then be used by other developers. Programmers can take the pain out of jumping, error prompts, and reconfiguring actions and focus on writing business logic.

The statute ModelDriven

With the above configuration, we can’t get completely out of configuration. For example, if we define an Action class that inherits from ActionSupport, we know that it is important to use ModelDriven to encapsulate user-uploaded data into a business Bean instead of declaring variables directly in the Action. I’m sure many readers have encountered this problem. When the business Bean is different, that is, the data that needs to be uploaded by the user is different, we need to add new actions accordingly, which directly results in changing the struts. XML configuration, and then changing the transaction configuration of applicationContext.xml, the Bean configuration, Why can’t we just define an Action and the Model-driven model changes dynamically?

Consider Struts2’s mechanics. Struts2 assembles client parameters into model-driven models through “assembly interceptors.” As long as we change the model object in ModelDriven before the assembly interceptor executes. This requires a custom interceptor, which Struts2 provides. In a custom interceptor, we create a model based on the Action method called by the user, and set the model to the Action so that the model can be dynamic. Remember, the interceptor needs to be placed in front of the defaultStack.

Also, the new question is how to dynamically select a business model based on the Action method. Repeat if method? Of course not. Dynamic models should come from dynamic methods. Therefore, defining action methods requires a specification, and this is how I define the specification in my own program. The Action methods are composed of the following: _$_, separated by a dollar character (which is a valid Java method identifier), followed by the name of the arbitrarily selected operation, followed by the class name of the business model. At the same time, all business models are put into a package named com.dw.business. So in the custom interception, we get the Action method name called by the user, we get the class name separated by the dollar character, the specified package name (in this case com.dw.business) + the class name is the full path of the business model class, and we use Java reflection to dynamically generate an empty business model object (so, The business model class must have a constructor with no arguments), set to Action, and handed to the assembler, the assembler will automatically assemble the model. The core code for the interceptor is shown in Listing 4.

Listing 4. Interceptor listing
public String intercept(ActionInvocation ai) throws Exception { ai.addPreResultListener(this); Main action = (Main)ai.getAction(); // Business method name String name = ai.getInvocationContext().getName(); int lastIndex = name.lastIndexOf("$"); if (lastIndex ! = -1) { try { String head = "com.dw.business."; String className = name.substring(lastIndex+1, name.length()); Action.setmodel (class.forname (head).newinstance ()); } catch (Exception e) {} } return ai.invoke(); }Copy the code

With the above configuration, we can basically block out most of the technical details of development, but we still have a problem. When an exception is accidentally thrown in a business method, Struts2 returns INPUT by default. As configured in Listing 1, errors are redirected to the noDir/error.jsp page. Displays error messages, which are suitable for non-Ajax processing. But sometimes we want it to return a JSON error, because some of the Action methods are Ajax calls, meaning that the result of the Action method needs to return errorJson. My solution is to take advantage of Java annotation technology and define a new annotation called IfErrorReturnToJson, which has the code shown in Listing 5.

Listing 5. The code for IfErrorReturnToJson
@Target(ElementType.METHOD) 
@Retention(RetentionPolicy.RUNTIME) 
public @interface IfErrorReturnToJson { 
 
}Copy the code

This annotation applies to the method, the business method of the Action. If we want a business method to return JSON when it sends an error, we just need to add this annotation to the method. Now, how do we dynamically modify the return value? The second line in Listing 2 adds a PreResultListener for the ActionInvocation, which does some processing before returning the result, which is exactly what we want. We implement the PreResultListener interface for our custom interceptor class, implementing the interface methods as shown in Listing 6.

Listing 6. Code for PreResultListener
public void beforeResult(ActionInvocation ai, String result) { if (Main.INPUT.equals(result) || Main.ERROR.equals(result)) { try { String methodName = ai.getInvocationContext().getName(); Method method = Main.class.getMethod(methodName); if (method ! = null && method.getAnnotation( IfErrorReturnToJson.class) ! = null) { ai.setResultCode(Main.ERROR_JSON); } } catch (Exception e) { } } }Copy the code

As you can see, before we return the result, if the current return is INPUT or ERROR, we use reflection to check if the method is annotated with IfErrorReturnToJson. If so, Call the ai. SetResultCode (Main ERROR_JSON); Method to modify the return value so that the result is a JSON data type.

Protocol for transaction isolation levels

In SSH development, the main consideration is Spring’s transaction configuration. Depending on the business, the transaction isolation level will be defined differently. For example, if the isolation level is high, Spring’s serialized read isolation configuration will be used. Some methods are just for permission control, for page jumps, and do not need transaction control; Some operations are intended to search, so the transaction is read-only. Fine-grained transaction configuration improves code efficiency for business processing. In this case, the naming convention of the business method can control the isolation level. For example, if the method name is * BEGIN *(the method name contains BEGIN), the method has no transaction control. If the method name is *search*, the method has a read-only transaction; If the method name is *seri*, serialized read transaction control is used to improve concurrency security. This transaction configuration list is shown.

Access code

In some applications, simple permission management is involved. Readers may think of some open source middleware, such as ralasafe open source permission components, although very comprehensive, but it takes a certain amount of time to learn, master, and SSH development needs more careful learning. When we only have simple permission control, we can use some protocol to complete user permission control. In the protocol I designed, I need to define a Permission table in the database, which represents permissions. It has a many-to-one relationship with the user table, and it has at least two fields, one is permissionName and one is permissionPrefix. PermissionName is used to describe this permission, and permissionPrefix is what we care about. We design our Action methods to follow principles like permissionPrefix_actionMethodName$BussinessBean, PermissionPrefix Prefix represents the permissionPrefix value of the permission table in the database, separated by underscores. After a user logs in, a list of permissions that the user has is stored in the session. When a user attempts to access an Action Method, the same Method is used in the ModelDriven specification, intercepting the Method name to obtain the access permission allowed for the Method. If the user’s permission list contains the permission for the Method, the call is allowed; otherwise, the call is not allowed. In this way, although the access to data cannot be achieved, it can satisfy the functional control of a large department. Keep in mind that the permission description can be changed, but the permissionPrefix for the permission cannot. If the method name does not have a permission identifier, it means that any user can access it. Similarly, if a method can be accessed by multiple permissions, you can design the method p1_p2_p3_methodName$BussinessBean to be separated by multiple underscores.

Make proper use of Struts2 parameter transfer technology

Due to the specificity of the HTTP protocol, values uploaded to the Web server are strings, and our business model is all object types, some of which are complex business objects. Struts2 provides a Conveter mechanism that allows programmers to convert strings into complex business objects. But when I first used this mechanism, it was exciting at first, but as the business changes, the business model changes, the administration of these conveters can be a real headache. Therefore, I believe that Conveter technology should be used as little as possible. I propose a new approach. Suppose a business model contains a List<People> pIDS attribute object, and People has an ID attribute. We need the client to upload a List of People ids into the List<People> pids. If using a Conveter, we need to define rules on the client side, such as client upload pids=1,2,3,4,6,8. The server side then uses the Conveter to split the string, create a People object, set the ID, add to the list, and so on. Sounds pretty giddy, doesn’t it? It is also relatively insecure and requires a lot of skill from the programmer in parsing strings. We appreciate the Struts2 parameter passing mechanism if we pass an argument like this: <input name= “pids[0].id” /><input name= “pids[1].id” /> Struts2 automatically assembles these two input uploads into a List<People> object based on the name of the uploaded data and assigns the value to pIDS. Pids [0] represents the 0th element in the List, using OGNL’s rules for passing arguments. It automatically identifies whether pids are arrays or lists, and if pIDS are empty by default, it automatically creates an empty array or list. I can check out the OGNL tutorial. Reasonable use of this technology can greatly reduce the difficulty of parameter transmission.

Use Struts2 tags wisely

Struts2 provides a wide range of tags, including UI tags, data tags, and logic tags. Here, we need to understand how UI tags interact with data on the server side, such as the CheckBoxList tag, which is passed to the server as an array, This array holds the values of the selected checkbox list, which is easier to retrieve than using the iterator tag to generate the checkbox list. The Doubleselect tag, used to generate cascades of 2 levels of SELECT, is often used for section-user cascades. The Optiontransferselect tag is used for batch migrations, such as assigning users to departments in batches. Using struts2 tags can greatly reduce user interface development and simplify the way data is transferred from the user interface to the server.

Model validation for Action

Struts2 has a complete validation mechanism. In the ActionSupport class, you can write model validation methods in the validate* method. If you override ActionSupport’s validate Method, the validate is called before any Action Method is executed. A custom validate* (* is the method name), such as add, is validateAdd. This validates only before the add method is executed. For data validation, I recommend using the Validater file, which is easier to configure and modify, and takes advantage of existing validators to reduce the pain of hard-coding validation. In the above framework, we need to create a new validator XML file named {class name}-{method name}-validation.xml in the same package as the Action class. The {class name}-validation.xml is sufficient. The final result is shown in Figure 2.

Figure 2. Validator file configuration results

For configuration details, you can search the Validation framework tutorial in Struts2.

Custom business model validators

Struts2 provides validators, including Date, Required, RequiredString, and so on, which can be attributed to data validation. For the specific business model validation, is more complex, therefore, in this framework, you can customize a validator, this is supported by the struts 2, it is responsible for the business model verification, such as you can verify the existence of users upload the user ID and this can ensure the security of the system to a great extent. The code for the validator is shown in Listing 7 below.

Listing 7. Business validator code
public class BusinessValidator extends FieldValidatorSupport { private String property = null; public String getProperty() { return property; } public void setProperty(String property) { this.property = property; } @Override public void validate(Object exist) throws ValidationException { String fieldName = getFieldName(); Object fieldValue = getFieldValue(fieldName, exist); if (fieldValue ! = null && fieldValue instanceof Integer && ((Integer)fieldValue) <= 0) {addFieldError("message", "Upload ID, this data does not exist!" ); } else if (fieldValue == null) {addFieldError("message", "Upload ID, this data does not exist!" ); } else { if (exist ! = null && exist instanceof Main && ((Main)exist).getModel() instanceof IChecker) { Main pa = (Main)exist; IChecker e = (IChecker)pa.getModel(); boolean isRight = e.checkOk(property, pa); if (! IsRight) {addFieldError("message", "upload ID, this data is not present!" ); } else { pa.getPubDao().getHibernateTemplate().clear(); } } } } }Copy the code

In Listing 7, an IChecker interface is used, and the business model to be verified needs to implement the IChecker interface. The business validation process is implemented in the interface implementation method, and false is returned in case of error.

In the SRC directory (or the class directory in web-INF), add the validater. XML file. After the other built-in validators, add the service validator configuration and name it check.

Listing 8. Configuration of the validator
< the validators >... <validator name="check" class="com.attendance.action.BusinessValidator"/> </validators>Copy the code

You can then use the validator to add the configuration shown in Listing 9 to the Validation file in Figure 2.

Listing 9. Use of validators
<field-validator type="check" short-circuit="true"> <param name="property"> Department </param> <message> This department does not exist! </message> </field-validator>Copy the code

Generate the JPA model of Hibernate using JEE Eclipse

IBM offers a JEE version of Eclipse specifically for developing Java EE applications, which provides the ability to generate jPA-compliant data models from databases. Following Hibernate3 support for the JPA specification (not fully, but fully), JEE Eclipse generation capabilities can be used to avoid cumbersome CONFIGURATION of HBM files. In addition, annotations such as NamedQuery and OrderBy can be provided in the JPA specification, which can reduce development time for complex databases.

The UI reuse

Struts2 action tag, used to invoke an action. The author thinks this tag is very useful, especially in UI reuse, such as user management, in many interfaces, are allowed to modify user information and user delete, so we can modify user information and user delete page code and JavaScript in a JSP, You also define an Action method (named A) that performs initialization for the JSP. This method is then called with the S :action tag in any interface that allows user modification or deletion, setting executeResult to true, and including the code that the user modified or deleted in the page. This enables Action reuse. If the reused interface does not require server initialization, this references the reused JSP directly using JSP :include or S :include.

summary

Through a series of explanations, this article describes how to build a general SSH development framework, stated the design ideas, due to space constraints, this article only introduces the framework of important parts and important ideas, I hope readers can benefit from it. Due to my limited level, if there is any mistake, please contact me for criticism and correction.

On the topic

  • See the “Struts2 official website” for the latest updates on Struts2.
  • Understand “Struts2 interceptor”, learn Struts2 interceptor, live learn and use.
  • See the article “Design and Implementation of role-function-Resource Based Permission Control Model” to learn the basic principles and implementation methods of permission control.
  • See “Spring’s Five Transaction Isolation levels and Seven Transaction propagation Behaviors” to learn about Spring’s transaction isolation levels and transaction propagation behaviors.
  • See the JPA specification to learn about the JPA specification and how to configure the Hibernate model using the JPA specification.
  • See “Java Reflection mechanism” to learn about Java reflection, which is the basis of many frameworks.
  • See “Java Annotations” to learn about Java annotation technology, which is based on reflection technology, JPA specifications can be configured based on annotations, and so on.
  • DeveloperWorks Java Technology zone: There are hundreds of articles on all aspects of Java programming.