This chapter first adds the functions that support annotations, so that the configuration file does not need to be modified frequently.
As for the view processing, just use json first and find time before writing.
Project address is:/hjx601496320/aMvc 。
The test code is:/hjx601496320/amvc-test 。
How to write it?
Because when I was writing code before, I divided the things I had to do in each class more clearly, so it was relatively simple to write when adding this function, and there were relatively few areas that needed to be modified.
What we need to do in this chapter are:
- Define an annotation, and identify the method of adding annotations in a certain class is a UrlMethodMapping.
- Modify the configuration file and add the package that needs to be scanned.
- Write a method to find all the classes in the package based on the value in the package.
- Add a new method to create UrlMethodMapping based on the annotation in the UrlMethodMapping factory class UrlMethodMappingFactory.
- In the init() method in Application, execute a new factory-class method based on whether annotation support is enabled.
- It's over.
How simple~~~
Start writing now
Define an annotation Request
Regarding how to customize the notes, you can search online, which is relatively simple. I'll just talk about it briefly here. I'll post the code first:
import ; import .*; /** * Indicates that the method in this class with the @Request annotation added is mapped to an http address. * * @author hjx */ @Documented @Target({, }) @Retention() public @interface Request { /** * Request type * Support GET, POST, DELETE, PUT * * @return */ RequestType[] type() default {, , , }; /** * Request address * When added on class, the value in value will be added before the value of @() on other methods as the base address. * * @return */ String value() default "/"; }
To define an annotation, you need to use the following things:
1: @interface: It means that this class is an annotation.
2: @Retention: The annotated retention strategy has several value ranges:
Code | illustrate |
---|---|
@Retention() | Annotations only exist in the source code |
@Retention() | The annotation will exist in the class bytecode file |
@Retention() | The annotation will exist in the class bytecode file, and can be obtained through reflection during runtime. |
Because we need to get customized annotations in the program, we use:.
3: @Target: It acts as a target, indicating where the annotation can be added, and the value range is:
Code | illustrate |
---|---|
@Target() | Interfaces, classes, enums, annotations |
@Target() | Fields, enumeration constants |
@Target() | method |
@Target() | Method parameters |
@Target() | Constructor |
@Target(ElementType.LOCAL_VARIABLE) | Local variables |
@Target(ElementType.ANNOTATION_TYPE) | annotation |
@Target() | Bag |
3: @Documented: This is mainly to keep custom annotations in the document, which has no practical significance and is generally added.
4: default: It is a default value for the attribute in the annotation (it looks like a method, or maybe it is a method, but I just call it a property, a little bit ~~~)
The above roughly talks about how to define an annotation. Now that the annotation is finished, let’s talk about the use of this annotation.
First of all, this annotation can be added to class and method. When added to the class, it means that the method in this class will be processed into a UrlMethodMapping, and the value attribute in it will be used as the basic address of all UrlMethodMappings in this class, and the type attribute does not work. When added to the method, it means that the method will be processed into a UrlMethodMapping, and the two attributes of the annotation play their normal role.
The annotation is finished, let’s change the configuration file below.
Modify the framework's configuration file
Just add a property, and the modified configuration file looks like this:
{ "annotationSupport": true, "annotationPackage": "", // "mapping": [ // { // "url": "/index", // "requestType": [ // "get" // ], // "method": "index", // "objectClass": "", // "paramTypes": [ // "", // "int" // ] // } // ] }
1: When the annotationSupport value is true, it means that the annotation is enabled.
2: annotationPackage represents the path of the package that needs to be scanned.
3: Because annotation support is enabled, in order to prevent repeated registration of UrlMethodMapping, I commented out the following configuration.
How to write a package scan
This method requires finding all the qualified class under the jar file and folder in the project, and recursion will be used. The code is in it, consisting of three methods, namely:
1:void getClassByPackage(String packageName, Set
This method receives two parameters, one is the package name packageName and the other is an empty Set (not null). After the method is executed, all the classes under the package will be filled into the Set. Here we mainly judge which types of files are in this package and process them separately according to the file type.
Note: If it is the type of a jar file, the filePath obtained is as follows:
file:/home/hjx/idea-IU/lib/idea_rt.jar!/com
You need to remove the head and tail, and then you can eat it, the chicken tastes good! Crispy~~ After processing, it looks like this:
/home/hjx/idea-IU/lib/idea_rt.jar
Here is the method code:
/** * Find all classes from the given registration * * @param packageName * @param classes */ @SneakyThrows({}) public static void getClassByPackage(String packageName, Set<Class> classes) { (classes); String packagePath = (DOT, SLASH); Enumeration<URL> resources = ().getResources(packagePath); while (()) { URL url = (); //File Type String protocol = (); String filePath = ((), CHARSET_UTF_8); if (TYPE_FILE.equals(protocol)) { getClassByFilePath(packageName, filePath, classes); } if (TYPE_JAR.equals(protocol)) { //The path to the file is taken filePath = ((":") + 1, ("!")); getClassByJarPath(packageName, filePath, classes); } } }
2:void getClassByFilePath(String packageName, String filePath, Set
Find all the qualified class in the folder and use recursion. You need to intercept the absolute path of the class file into the fully qualified name of the class. The code looks like this:
/** * Recursively find the class in the package in the folder * * @param packageName * @param filePath * @param classes */ static void getClassByFilePath( String packageName, String filePath, Set<Class> classes ) { File targetFile = new File(filePath); if (!()) { return; } if (()) { File[] files = (); for (File file : files) { String path = (); getClassByFilePath(packageName, path, classes); } } else { //If it is a class file boolean trueClass = (CLASS_MARK); if (trueClass) { //Extract the complete class name filePath = (SLASH, DOT); int i = (packageName); String className = (i, () - 6); // Not an internal class boolean notInnerClass = ("$") == -1; if (notInnerClass) { //Load class object according to class name Class aClass = (className); if (aClass != null) { (aClass); } } } } }
3:void getClassByJarPath(String packageName, String filePath, Set
Find all the qualified class in the jar file. Nothing to say, here is the code:
/** * Find the class in the package in the folder in the jar file * * @param packageName * @param filePath * @param classes */ @SneakyThrows({}) static void getClassByJarPath( String packageName, String filePath, Set<Class> classes ) { JarFile jarFile = new URLJarFile(new File(filePath)); Enumeration<JarEntry> entries = (); while (()) { JarEntry jarEntry = (); String jarEntryName = ().replace(SLASH, DOT); //class under package boolean trueClass = (CLASS_MARK) && (packageName); // Not an internal class boolean notInnerClass = ("$") == -1; if (trueClass && notInnerClass) { String className = (0, () - 6); (className); //Load class object according to class name Class aClass = (className); if (aClass != null) { (aClass); } } } }
In this way, you will finish getting the class under the package name~
Modify UrlMethodMappingFactory
Here is a new method:
List, just use the Class object obtained after scanning the package as a parameter and return a UrlMethodMapping collection. The code is as follows:
/** * Get the map by parsing Class * * @param aClass * @return */ public List<UrlMethodMapping> getUrlMethodMappingListByClass(Class<Request> aClass) { List<UrlMethodMapping> mappings = new ArrayList<>(); Request request = (); if (request == null) { return mappings; } String basePath = (); for (Method classMethod : ()) { UrlMethodMapping urlMethodMapping = getUrlMethodMappingListByMethod(classMethod); if (urlMethodMapping == null) { continue; } //Add path in Request on class as the base path String url = (basePath + "/" + ()); (url); (urlMethodMapping); } return mappings; } /** * Get the map by parsing the Method * Annotation Request jumps out when it does not exist * * @param method * @return */ private UrlMethodMapping getUrlMethodMappingListByMethod(Method method) { Request request = (); if (request == null) { return null; } Class<?> declaringClass = (); String path = (); for (char c : ()) { (c != ' ', declaringClass + "." + () + "Request path exception:" + path + " !"); } return getUrlMethodMapping( path, (), declaringClass, method, () ); }
Here I checked the value of the value in the annotation Request, and an exception will be thrown if there is a space in the middle. () This method mainly removes the excess "/" in the url, and the code looks like this:
private static final String SLASH = "/"; /** * Process url * 1: Remove the adjacent and repeated "/" in the connection, * 2: If there is no "/" at the beginning of the link, it will be added. * 3: If there is "/" at the end of the link, remove it. * * @param url * @return */ public static String makeUrl(@NonNull String url) { char[] chars = (); StringBuilder newUrl = new StringBuilder(); if (!(SLASH)) { (SLASH); } for (int i = 0; i < ; i++) { if (i != 0 && chars[i] == chars[i - 1] && chars[i] == '/') { continue; } if (i == - 1 && chars[i] == '/') { continue; } (chars[i]); } return (); }
In this way, the factory method of obtaining the UrlMethodMapping through annotations is finished. The following begins to modify the code of the loading framework.
Modify init in Application
Here, because a method to obtain UrlMethodMapping using annotation method is added, a new method is created:
void addApplicationUrlMappingByAnnotationConfig(JSONObject configJson) . Here we get the package name in the framework configuration and do some configuration verification. The code is as follows:
/** * Use annotations to load UrlMethodMapping * * @param configJson */ private void addApplicationUrlMappingByAnnotationConfig(JSONObject configJson) { String annotationPackage = (ANNOTATION_PACKAGE_NODE); (annotationPackage, ANNOTATION_PACKAGE_NODE + NOT_FIND); //Get the class with @Request added Set<Class> classes = new HashSet<>(); (annotationPackage, classes); Iterator<Class> iterator = (); while (()) { Class aClass = (); List<UrlMethodMapping> mappings = (aClass); if (() == 0) { continue; } for (UrlMethodMapping mapping : mappings) { addApplicationUrlMapping(mapping); } } }
Then extract the previously written code to read json configuration to generate urlMappin and write a separate method:
void addApplicationUrlMappingByJsonConfig(JSONObject configJson), so that the functions of each method in the code are independent, making it look neat and clear. The code is as follows:
/** * Use file configuration to load UrlMethodMapping * If it cannot be found in the configuration, it will not be executed. * * @param configJson */ private void addApplicationUrlMappingByJsonConfig(JSONObject configJson) { JSONArray jsonArray = (MAPPING_NODE); if (jsonArray == null || () == 0) { return; } for (int i = 0; i < (); i++) { UrlMethodMapping mapping = ((i)); addApplicationUrlMapping(mapping); } }
Finally, just modify init() slightly. After modifying it, it looks like this:
/** * Initialize configuration */ @SneakyThrows() protected void init() { String configFileName = applicationName + ".json"; InputStream inputStream = ().getResourceAsStream(configFileName); byte[] bytes = new byte[()]; (bytes); String config = new String(bytes, "utf-8"); //Application configuration JSONObject configJson = (config); //TODO: The factory class that generates the object (first defaults to new one every time) = new AlwaysNewObjectFactory(); //TODO: Different parameter names to get the class (currently default is asm) (new AsmParamNameGetter()); //Load through file configuration addApplicationUrlMappingByJsonConfig(configJson); //Whether to enable annotation support Boolean annotationSupport = (ANNOTATION_SUPPORT_NODE); (annotationSupport, ANNOTATION_SUPPORT_NODE + NOT_FIND); if (annotationSupport) { addApplicationUrlMappingByAnnotationConfig(configJson); } }