Dynamics of Java programming, Part 3: application reflection — reprint

In, I introduced the java reflection API and briefly described some of its basic functions. I also carefully studied the performance of reflection and gave some guidelines at the end of the article to tell readers when reflection should be used and when it should not be used in an application. In this month's article, I will discuss this issue in more depth by looking at an application, a library for command-line parameter processing, which can well reflect the strengths and weaknesses of reflection.

At the beginning, before I really start writing implementation code, I will first define the problem to be solved, and then design an interface for the library. However, when developing this library, I did not follow the above steps - I first tried to simplify and generalize the existing code in a group of applications with a common code base. The linear sequence of "define design build" used in this article is much simpler than completely describing the development process. Moreover, by organizing the description of the development process in this way, I can correct some of my original assumptions and clean up some unnecessary aspects of the code in this library. Hopefully, you'll find the above approach useful as a model for developing your own reflection based applications.

I've written many Java applications that use command line arguments. At first, most applications were small, but in the end, some applications became large beyond my expectation. Here's the standard pattern I've observed for the scaling up of these applications: there's only one or two parameters at first, in a specific order. Considering that the application has more to do, more parameters are added. Tired of entering all the parameters every time, we make some parameters optional and let them have default values. Forgetting the order of the parameters, the code is modified to allow the parameters to be arranged in any order. Give the application to others who are interested. However, they do not know what these parameters represent, so they add more perfect error checking and "help" description for these parameters. When I get to step 5, I usually regret not putting the whole process in the first step. Fortunately, I will soon forget the later stages. In less than a week or two, I will consider another simple small command-line program. I want to have this application. With this idea, it is only a matter of time before the whole disgusting cycle is reproduced. There are some libraries that can be used to help with command-line parameter processing. However, in this article, I will ignore these libraries and create one myself. This is not (or just) because I have a "not invented here" attitude I don't want to use things invented by outsiders, but because I want to take parameter processing as an example. In this way, the strengths and weaknesses of reflection reflect the demand for parameter processing library. In particular, parameter processing library: it needs a flexible interface to support various applications. For each application, it must be easy to configure. Don't Seek top-level performance, because parameters only need to be processed once. There is no access security issue because command-line applications typically run without a security manager. The actual reflection code in this library represents only a small part of the entire implementation, so I'll focus on some of the most relevant aspects of reflection. If you want to find out more about this library (and perhaps use it in your own simple command-line application), you can find a link to the web site in the section.

Perhaps the most convenient way for an application to access parameter data is through some fields of the application's main object. For example, suppose you are writing an application for generating a business plan. You may want to use a Boolean tag to control whether the business plan is brief or lengthy. Use an int as the first year's revenue and a string as the product description. I'll call these variables that affect the operation of the application parameters to match the arguments provided on the command line (arguments) -- that is, the values of formal parameters are distinguished. By using fields for these formal parameters, they can be easily called anywhere in the application code that needs formal parameters. Moreover, if fields are used, it is also convenient to set default values for any formal parameters when defining formal parameter fields, as shown in Listing 1:

Reflection will allow the application to directly access these private fields, allowing the parameter processing library to set the value of the parameter without any special hooks in the application code. But I do need some way for the library to associate these fields with specific command-line parameters. Before I can define how this association between a parameter and a field communicates with the library, I need to decide how I want to format these command-line parameters. For this article, I'll define a command line format, a simplified version of UNIX conventions. The argument values of formal parameters can be provided in any order, Use a hyphen at the beginning to indicate that an argument gives one or more single character formal parameter tags (relative to the value of the actual formal parameter). For this business plan generator, I will use the following formal parameter tag characters: C -- brief plan f -- first year revenue (thousands of dollars) g -- growth rate (annually) n -- Product Name Boolean parameter can set a value only by marking the character itself, while other types of formal parameters also need some additional argument information. For numerical arguments, I only put its value immediately after the marking character of the formal parameter (this means that numbers cannot be used as marker characters), and for formal parameters with string type values, I will use the argument following the marker character as the actual value on the command line. Finally, if some formal parameters are needed (for example, the file name of the output file of the business plan generator). I assume that the actual parameter values of these formal parameters follow the optional formal parameter values in the command line. With these conventions given above, the command line of the business plan generator looks like this: Java plangen - C - f2500 - g2.5 - n "isue4u - restriction at Internet speed" plan. Txt if you put it together, each argument means: - C -- generate brief plan - f2500 -- the first year revenue is $2500000 -g2 5 -- the annual growth rate is 250% - n "isue4u..."-- The product name is "isue4u..." plan. Txt -- the required output file name. At this time, I have obtained the specification of the basic functions of the parameter processing library. The next step is to define a specific interface for the application code to use the library. You can use a single call to take care of the actual processing of command-line parameters, but the application first needs to define its specific formal parameters into the library in some way. These formal parameters can have several different types (for the business plan generator example, the types of formal parameters can be Boolean, int, float and java.lang.string). Each type may have some special requirements. For example, if the tag character is given, it is better to define the Boolean formal parameter as false instead of always defining it as true. Moreover, it is also necessary to define a valid range for an int value Very useful. My approach to dealing with these different requirements is to first define and use a base class for all formal parameters, and then subdivide the base class for each specific type of formal parameter. This method allows applications to provide parameter definitions to the library in the form of an array of instances of the basic parameter definition class, while the actual definition can use subclasses that match each parameter type. For the business plan generator example, this can take the form shown in Listing 2: with the allowed formal parameters defined in an array, the application's call to the parameter processing code can be as simple as a single call to a static method. In order to allow additional arguments (either required values or variable length values) in addition to those defined in the shape parameter group, I will make this call return the actual number of arguments processed. This way, the application can check the additional arguments and use them appropriately. The final result looks like listing 3: public static void main (string [] args) { // if no arguments are supplied,assume help is needed if (args.length > 0) { // process arguments directly to instance PlanGen inst = new PlanGen(); int next = ArgumentProcessor.processArgs (args,PARM_DEFS,inst); // next unused argument is output file name if (next >= args.length) { System.err.println("Missing required output file name") ; System. exit(1); } File outf = new File(args[next++]); ... } else { System.out.println("\nUsage: java PlanGen " + "[-options] file\nOptions are:\n c concise plan\n" + "f first year revenue (K$)\n g growth rate\n" + "n product description"); } } } The last remaining part is dealing with error reports (for example, an unknown parameter marker character or an out of range numeric value). For this purpose, I will define argumenterrorexception as an unchecked exception. If an error of this kind occurs, this exception will be thrown. If this exception is not caught, it will immediately close the application and send an error message to the stack Trace output to the console. As an alternative, you can also catch this exception directly in the code, And handle exceptions in other ways (for example, a real error message may be output along with the usage information). In order for this library to use reflection as planned, it needs to find some fields specified by the formal parameter definition array, and then store the appropriate values in these fields from the corresponding command-line parameters. This task can be done by finding only the field information required by the actual command-line parameters But I chose to separate search and use. I will find all the fields in advance, and then use only the information that has been found during parameter processing. Finding all the fields in advance is an error proof programming step, which eliminates a potential problem when using reflection. If I just find the fields I need, Then it is easy to break a formal parameter definition (for example, enter the wrong field name), and you can't recognize that an error has occurred. There will be no compile time error here, because the field name is passed as a string, and the program can execute well as long as the command line does not specify an argument that matches the broken formal parameter definition. This kind of hoodwinked error can easily lead to the release of imperfect code. False Suppose I want to find the field information before actually processing the argument. Listing 4 shows the implementation of the base class for parameter definition, which has a bindtoclass () method to process the field search. protected ParameterDef(char chr,String name) { m_char = chr; m_name = name; } public char getFlag() { return m_char; } protected void bindToClass(Class clas) { try { // handle the field look up and accessibility m_field = clas.getDeclaredField(m_name); m_field.setAccessible(true); } catch (NoSuchFieldException ex) { throw new IllegalArgumentException("Field '" + m_name + "' not found in " + clas.getName()); } } public abstract void handle(ArgumentProcessor proc); } The actual library implementation also involves several classes not mentioned in this article. I'm not going to introduce each class one by one, because most of them are not related to the reflection aspect of the library. I will mention that I chose to save the target object as a field of the argumentprocessor class and implement the real setting of a formal parameter field in this class. This method provides a simple pattern for parameter processing: the argumentprocessor class scans the arguments to find parameter tags, finds the corresponding parameter definition for each tag (always a subclass of parameterdef), and then calls the handle () method of this definition. After interpreting the argument value, the handle () method calls the setValue () method of argumentprocessor. Listing 5 shows the incomplete version of the argumentprocessor class, including the formal parameter binding call in the constructor and the setValue () method: / / bind all parameters to target class for (int i = 0; I < parms. Length; I + +) {parms [i]. Bindtoclass (target. Getclass());}// save target object for later use m_ targetObject = target; } public void setValue(Object value,Field field) { try { // set parameter field value using reflection field.set(m_targetObject,value); } catch (illegalaccessexception ex) { throw new IllegalArgumentException("Field " + field.getName() + " is not accessible in object of class " + m_targetObject.getClass().getName()); } } public void reportArgumentError(char flag,String text) { throw new ArgumentErrorException(text + " for argument '" + flag + "' in argument " + m_currentIndex); } public static int processArgs(String[] args,ParameterDef[] parms,Object target) { ArgumentProcessor inst = new ArgumentProcessor(parms,target); ... } } Finally, Listing 6 shows a partial implementation of the parameter definition subclass of the int parameter value. This includes overloading the bindtoclass() method (from) of the base class. This overloaded method first calls the implementation of the base class and then checks whether the found fields match the expected type. Subclasses of other specific parameter types (Boolean, float, string, etc.) are very similar. Int parameter defines the class public intdef (char Chr, string name, int min, int max) { super(chr,name); m_min = min; m_max = max; } protected void bindToClass(Class clas) { super.bindToClass(clas); Class type = m_field.getType(); if (type != Integer.class && type != Integer.TYPE) { throw new IllegalArgumentException("Field '" + m_name + "'in " + clas.getName() + " is not of type int"); } } public void handle(ArgumentProcessor proc) { // set up for validating boolean minus = false; boolean digits = false; int value = 0; // convert number supplied in argument list to 'value' ... // make sure we have a valid value value = minus ? -value : value; if (!digits) { proc.reportArgumentError(m_char,"Missing value"); } else if (value < m_min || value > m_max) { proc.reportArgumentError(m_char,"Value out of range"); } else { proc.setValue(new Integer(value),m_field); } } } In this article, I describe the design process of a library for processing command-line parameters as a practical example of reflection. This library is a good illustration of how to use reflection effectively -- it simplifies application code without significantly sacrificing performance. How much performance has been sacrificed? From some quick tests of my development system, it can be seen that a simple test program runs only 40 milliseconds slower when it uses the whole library for parameter processing than when it does not carry any parameter processing. Most of this extra time is spent loading library classes and other classes used by library code, so even for applications that define many command-line formal parameters and many argument values, it is unlikely to be much worse than this result. For my command-line application, the extra 40 milliseconds didn't get my attention at all. You can find the complete library code through the links in. It includes some features I didn't mention in this article, including such details, such as hooks,
The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>