review

I have written a blog before about extracting servlets, but every time I use it, I have to add method= XXX. Now I want to improve the method based on the previous method, and achieve the effect similar to SpringMVC, and directly find the corresponding method to execute according to the request. So now it’s an annotated improvement on the previous Serlvet extraction

thought

  1. Create a servlet and let all requests go through the servlet
  2. In this servlet, annotate the class that wrote the method that maps the address (this class is annotated)
  3. Then find the method defined in this class, and call the method with the mapped address through reflection

Code and interpretation

  • Tool class ClassScaner: used to find all the classes under a package
public class ClassScanner { 
    /** * get all classes * under the package@param
     * @returnList contains all class instances */
    public staticList<Class<? >> getClasssFromPackage(String packageName) { List<Class<? >> clazzs =new ArrayList<>();
        // Whether to loop search subpackages
        boolean recursive = true;
        // The path name corresponding to the package name
        String packageDirName = packageName.replace('. '.'/');
        Enumeration<URL> dirs;
        try {
            dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
            while (dirs.hasMoreElements()) {
                URL url = dirs.nextElement();
                String protocol = url.getProtocol();
                if ("file".equals(protocol)) {
                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8"); findClassInPackageByFile(packageName, filePath, recursive, clazzs); }}}catch (Exception e) {
            e.printStackTrace();
        }
        return clazzs;
    }

    /** * find all classes */ in the path corresponding to package
    public static void findClassInPackageByFile(String packageName, String filePath, final booleanrecursive, List<Class<? >> clazzs) {
        File dir = new File(filePath);
        if(! dir.exists() || ! dir.isDirectory()) {return;
        }
        // Find all files in the given directory and filter them conditionally
        File[] dirFiles = dir.listFiles(new FileFilter() {

            public boolean accept(File file) {
                boolean acceptDir = recursive && file.isDirectory();// Accept dir
                boolean acceptClass = file.getName().endsWith("class");// Accept the class file
                returnacceptDir || acceptClass; }});for (File file : dirFiles) {
            if (file.isDirectory()) {
                findClassInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive, clazzs);
            } else {
                String className = file.getName().substring(0, file.getName().length() - 6);
                try {
                    clazzs.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + "." + className));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
Copy the code
  • DispatherServlet: Gets all *.do requests and distributes them to the appropriate method
@WebServlet("*.do")
public class DispatherServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String requestURI = req.getRequestURI();
        String contextPath = req.getContextPath();
        // In order to get the requested address, we need the part, mainly in order to get the IP port item name and other irrelevant information
        String mappingPath = requestURI.substring(contextPath.length(),requestURI.length());

		// Find all the classes in the Controller packageList<Class<? >> classsFromPackage = ClassScanner.getClasssFromPackage("com.kehao.controller");
		
		// For each class see if it has a MyCompoent annotation
        classsFromPackage.stream().filter(clz-> clz.isAnnotationPresent(MyCompoent.class)).forEach(
        		// For eligible classes, get their method annotated by the MyRequestMapping annotation and call it if the value of the annotation is the same as the request address
                (clz)->{
                    Arrays.stream(clz.getDeclaredMethods()).
                            filter(method -> method.isAnnotationPresent(MyRequestMapping.class)).
                            filter(method->mappingPath.equals(method.getAnnotation(MyRequestMapping.class).value())).
                            forEach(method -> {
                                try {
                                    method.invoke(clz.newInstance(),req,resp);
                                } catch(Exception e) { e.printStackTrace(); }}); }); }}Copy the code
  • HelloController Specifies the actual control layer
@MyCompoent
public class HelloController {

    @MyRequestMapping("/hello.do")
    public void hello(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        resp.getWriter().println("hello,i'm in HelloController"); }}Copy the code
  • annotations
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRequestMapping {
    String value(a);
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyCompoent {
}
Copy the code

There are still shortcomings

Obviously, this code has significant shortcomings and needs to be improved. First, classes and methods need to be scanned for each call, and an object needs to be created. These processes are repeated and can be optimized