In the previous

Write your own MVC framework (2)

Write your own MVC framework

The project address is: github.com/hjx60149632… .

The test code is at: github.com/hjx60149632… .

Get parameters according to Method and convert parameter types

In the previous article, we created a mapping between url and Method, and successfully created the mapping. In this article, we will get parameters based on the input parameter name and parameter type of Method, and convert parameter types to conform to the definition of Method.

Instructions in advance

Because this is only a simple implementation of MVC framework, only to achieve the basic data type and basic data type wrapper class conversion, there is no very complex data binding like Spring. So I put some strong checks on the code.

Now start writing

When we get a parameter from an HTTP request, we usually need to know the name of the parameter. The parameter name can be the input parameter name of the method. This step we are ready to (watch an article: www.cnblogs.com/hebaibai/p/… .

Here we need to define a Method that converts the String parameter in the request to the input type of the Method we define. Look at the method definition in Servletrequest.java to see why the parameters in the request are strings. I won’t talk about it here. So our method could look like this:

/** * Get the method input parameter **@param valueTypes
 * @param valueNames
 * @param valueSource
 * @return* /
publicObject[] getMethodValue(Class[] valueTypes, String[] valueNames, Map<String, String[]> valueSource){... }Copy the code

Three arguments are accepted here

1: valueTypes This is the input type of Method

2: valueNames This is the input parameter name of Method

3: valueSource This is all the parameters in a request. This parameter is a Map. The generic type for value is an array of strings, which is used because multiple parameters with the same name can occur in a single request. You can look at the methods in Servletrequest.java:

public String[] getParameterValues(String name);
Copy the code

explain

I’m still not going to write the Servlet here, because we can write the entry at the end after the entire code shelf has been written and every part has been unit-tested. Just like building blocks, get each piece ready and then put everything back together.

Continue to

Now that the method is defined, how to convert a String parameter based on the parameter type is a troublesome matter. There is a lot of if and else code to be written here. Let’s do it step by step, starting with a basic data type conversion.

Data type conversion

Here we define an interface, Valueconverter.java, that contains only one method for data conversion

<T> T converter(String[] value, Class<T> valueClass);
Copy the code

Now, why is it defined as an interface? Why not just write a Class with the implementation code in it?

Because I also have a factory class to get the implementation of Valueconverter.java. The factory class looks like this

/** * Data converter factory class */
public class ValueConverterFactory {

    /** * Gets the converter ** based on the target type@param valueClass
     * @return* /
    public static ValueConverter getValueConverter(Class valueClass) {
        if(...). {return ValueConverter;
        }
        throw new UnsupportedOperationException("Data type:" + valueClass.getName() + "Conversion is not supported!"); }}Copy the code

Why write this factory class? The valueconverter.java interface (interface) is not defined for the purpose of writing a service or DAO to make the code look good in development, but for us to define standards. Specify the input and output parameters, exception information, method name, and what the method is used for in this standard for each method. Any class that implements this interface must comply with this standard. The caller also does not need to know which implementation class it is calling, as long as it passes the parameter according to the interface standard, it can get the desired output parameter.

So all we need to do with this code is pass a Class to ValueConverterFactory, and the factory Class returns an implementation that can convert this Class.

Adding the above getMethodValue looks like this:

/** * Get the method input parameter **@param valueTypes
 * @param valueNames
 * @param valueSource
 * @return* /
public Object[] getMethodValue(Class[] valueTypes, String[] valueNames, Map<String, String[]> valueSource) {
    Assert.notNull(valueTypes);
    Assert.notNull(valueNames);
    Assert.notNull(valueSource);
    Assert.isTrue(valueNames.length == valueTypes.length,
            "Inconsistent getMethodValue() length!");
    int length = valueNames.length;
    Object[] values = new Object[length];
    for (int i = 0; i < values.length; i++) {
        Class valueType = valueTypes[i];
        String valueName = valueNames[i];
        String[] strValues = valueSource.get(valueName);
        // The source parameter key does not exist or the key value does not exist. Set the value to null
        if (strValues == null) {
            values[i] = null;
            continue;
        }
        ValueConverter valueConverter = ValueConverterFactory.getValueConverter(valueType);
        Object converter = valueConverter.converter(strValues, valueType);
        values[i] = converter;
    }
    return values;
}
Copy the code

As you can see here, when we call the getValueConverter method of the factory class, the factory class will give us a converter ValueConverter, and we just need to use it to do the conversion without knowing how to do the conversion.

But we’ll write a few converters first, because there are no real converters available right now, just standards. Now let’s write a converter for the basic data type.

Conversion of basic data types

Here, we use Class to determine if it is a basic type. Note:

When I say basic data types, I mean the basic data types in Java and their wrapper classes and strings

Start with a utility class:

public class ClassUtils {

    /** * Java base type */
    public static List<Class> JAVA_BASE_TYPE_LIST = new ArrayList<>();

    public final static Class INT_CLASS = int.class;
    public final static Class LONG_CLASS = long.class;
    public final static Class FLOAT_CLASS = float.class;
    public final static Class DOUBLE_CLASS = double.class;
    public final static Class SHORT_CLASS = short.class;
    public final static Class BYTE_CLASS = byte.class;
    public final static Class BOOLEAN_CLASS = boolean.class;
    public final static Class CHAR_CLASS = char.class;
    public final static Class STRING_CLASS = String.class;
    public final static Class INT_WRAP_CLASS = Integer.class;
    public final static Class LONG_WRAP_CLASS = Long.class;
    public final static Class FLOAT_WRAP_CLASS = Float.class;
    public final static Class DOUBLE_WRAP_CLASS = Double.class;
    public final static Class SHORT_WRAP_CLASS = Short.class;
    public final static Class BOOLEAN_WRAP_CLASS = Boolean.class;
    public final static Class BYTE_WRAP_CLASS = Byte.class;
    public final static Class CHAR_WRAP_CLASS = Character.class;

    static {
        // Basic data type
        JAVA_BASE_TYPE_LIST.add(INT_CLASS);
        JAVA_BASE_TYPE_LIST.add(LONG_CLASS);
        JAVA_BASE_TYPE_LIST.add(FLOAT_CLASS);
        JAVA_BASE_TYPE_LIST.add(DOUBLE_CLASS);
        JAVA_BASE_TYPE_LIST.add(SHORT_CLASS);
        JAVA_BASE_TYPE_LIST.add(BYTE_CLASS);
        JAVA_BASE_TYPE_LIST.add(BOOLEAN_CLASS);
        JAVA_BASE_TYPE_LIST.add(CHAR_CLASS);
        // Basic data type (object)
        JAVA_BASE_TYPE_LIST.add(STRING_CLASS);
        JAVA_BASE_TYPE_LIST.add(INT_WRAP_CLASS);
        JAVA_BASE_TYPE_LIST.add(LONG_WRAP_CLASS);
        JAVA_BASE_TYPE_LIST.add(FLOAT_WRAP_CLASS);
        JAVA_BASE_TYPE_LIST.add(DOUBLE_WRAP_CLASS);
        JAVA_BASE_TYPE_LIST.add(SHORT_WRAP_CLASS);
        JAVA_BASE_TYPE_LIST.add(BOOLEAN_WRAP_CLASS);
        JAVA_BASE_TYPE_LIST.add(BYTE_WRAP_CLASS);
        JAVA_BASE_TYPE_LIST.add(CHAR_WRAP_CLASS);
    }

    /** * Check for basic data types (including wrapper classes for basic data types) **@param aClass
     * @return* /
    public static boolean isBaseClass(Class aClass) {
        int indexOf = JAVA_BASE_TYPE_LIST.indexOf(aClass);
        returnindexOf ! = -1; }... }Copy the code

You just need to determine if the Class is in JAVA_BASE_TYPE_LIST.

We will start to write data conversion, because the basic type of the wrapper class basically has a direct conversion method, we call one by one, the code looks like this:


/** * Basic data type conversion **@author hjx
 */
public class BaseTypeValueConverter implements ValueConverter {

    /** * is not an array type@param value
     * @param valueClass
     * @param <T>
     * @return* /
    @Override
    public <T> T converter(String[] value, Class<T> valueClass) { Assert.notNull(value); Assert.isTrue(! valueClass.isArray(),"ValueClass cannot be an array type!");
        String val = value[0];
        Assert.notNull(val);
        if (valueClass.equals(ClassUtils.INT_CLASS) || valueClass.equals(ClassUtils.INT_WRAP_CLASS)) {
            Object object = Integer.parseInt(val);
            return (T) object;
        }
        if (valueClass.equals(ClassUtils.LONG_CLASS) || valueClass.equals(ClassUtils.LONG_WRAP_CLASS)) {
            Object object = Long.parseLong(val);
            return (T) object;
        }
        if (valueClass.equals(ClassUtils.FLOAT_CLASS) || valueClass.equals(ClassUtils.FLOAT_WRAP_CLASS)) {
            Object object = Float.parseFloat(val);
            return (T) object;
        }
        if (valueClass.equals(ClassUtils.DOUBLE_CLASS) || valueClass.equals(ClassUtils.DOUBLE_WRAP_CLASS)) {
            Object object = Double.parseDouble(val);
            return (T) object;
        }
        if (valueClass.equals(ClassUtils.SHORT_CLASS) || valueClass.equals(ClassUtils.SHORT_WRAP_CLASS)) {
            Object object = Short.parseShort(val);
            return (T) object;
        }
        if (valueClass.equals(ClassUtils.BYTE_CLASS) || valueClass.equals(ClassUtils.BYTE_WRAP_CLASS)) {
            Object object = Byte.parseByte(val);
            return (T) object;
        } 
        if (valueClass.equals(ClassUtils.BOOLEAN_CLASS) || valueClass.equals(ClassUtils.BOOLEAN_WRAP_CLASS)) {
            Object object = Boolean.parseBoolean(val);
            return (T) object;
        }
        if(valueClass.equals(ClassUtils.CHAR_CLASS) || valueClass.equals(ClassUtils.CHAR_WRAP_CLASS)) { Assert.isTrue(val.length() = =1.Char cannot be converted because parameter length is abnormal!);
            Object object = val.charAt(0);
            return (T) object;
        }
        if (valueClass.equals(ClassUtils.STRING_CLASS)) {
            Object object = val;
            return (T) object;
        }
        throw new UnsupportedOperationException("Type exception, non-basic data type!"); }}Copy the code

This is where the basic data type conversion is written. The next step is to deal with arrays, because we declared that only primitive data types are converted, so arrays can only be primitive data types.

Conversion of an array of primitive data types

So how do you tell if a Class is an array? I searched the Java Class API and found two methods

// Check if it is an array
public native boolean isArray(a);

// If Class is an array, return the Class of the elements in the array
// Return null if Class is not an array
public nativeClass<? > getComponentType();Copy the code

Now you can write the code


import com.hebaibai.amvc.utils.Assert;

/** * Basic data type conversion **@author hjx
 */
public class BaseTypeArrayValueConverter extends BaseTypeValueConverter implements ValueConverter {

    @Override
    public <T> T converter(String[] value, Class<T> valueClass) {
        Assert.notNull(value);
        Assert.notNull(valueClass);
        Assert.isTrue(valueClass.isArray(), "ValueClass must be an array type!"); Class componentType = valueClass.getComponentType(); Assert.isTrue(! componentType.isArray(),"ValueClass does not support multiples!");
        Object[] object = new Object[value.length];
        for (int i = 0; i < value.length; i++) {
            object[i] = super.converter(new String[]{value[i]}, componentType);
        }
        return(T) object; }}Copy the code

And that completes the two converters.

BUT

Now there are only converters in the factory class. According to what logic, what converters have not been written


import com.hebaibai.amvc.utils.ClassUtils;

/** * Data converter factory class */
public class ValueConverterFactory {

    /** * Data conversion of the basic data type */
    private static final ValueConverter BASE_TYPE_VALUE_CONVERTER = new BaseTypeValueConverter();
    /** * Data conversion of primitive array */
    private static final ValueConverter BASE_TYPE_ARRAY_VALUE_CONVERTER = new BaseTypeArrayValueConverter();

    /** * Gets the converter ** based on the target type@param valueClass
     * @return* /
    public static ValueConverter getValueConverter(Class valueClass) {
        boolean baseClass = ClassUtils.isBaseClass(valueClass);
        if (baseClass) {
            return BASE_TYPE_VALUE_CONVERTER;
        }
        if (valueClass.isArray()) {
            return BASE_TYPE_ARRAY_VALUE_CONVERTER;
        }
        throw new UnsupportedOperationException("Data type:" + valueClass.getName() + "Conversion is not supported!"); }}Copy the code

Then everything will be all right

Say some what

If you want to add other conversions later, you just need to write a few new implementation classes and modify the factory code to make it easier to extend. That’s why I wrote the factory class.

Write some code to test it out

MethodValueGetter methodValueGetter = new MethodValueGetter();
// Assemble test data
Map<String, String[]> value = new HashMap<>();
value.put("name".new String[]{"What for nothing?"});
value.put("age".new String[]{"20"});
value.put("children".new String[]{He Dabai 1."He Dabai 2"."He Dabai 3"."He Dabai 4"});
// Execute method
Object[] methodValue = methodValueGetter.getMethodValue(
        new Class[]{String.class, int.class, String[].class},// Enter the parameter type
        new String[]{"name"."age"."children"},// The name of the input parameter
        value// Parameters in the request
);
// Print the result
for (int i = 0; i < methodValue.length; i++) {
    Object obj = methodValue[i];
    if (obj == null) {
        System.out.println("null");
        continue; } Class<? > objClass = obj.getClass();if (objClass.isArray()) {
        Object[] objects = (Object[]) obj;
        for (Object object : objects) {
            System.out.println(object + "= = ="+ object.getClass()); }}else {
        System.out.println(obj + "= = ="+ obj.getClass()); }}Copy the code

Results:

String 20===class java.lang.Integer He Dabai 1===class java.lang.String He Dabai 2===class java.lang 3===class java.lang.String 4===class java.lang.StringCopy the code

The test succeeded

The last

Now that we’ve got the parameters to execute Method by reflection, we’re going to execute it. I’ll write the next article

Bye bye

By the way, the code is updated synchronously on my Github github.com/hjx60149632… Welcome to see ~

After the

The next article

Write your own MVC framework (4)