抄写springboot源码--my-spring项目☞注解实现接口调用

这篇博客详细介绍了如何在SpringBoot中通过自定义注解实现接口调用的包扫描、接口映射以及统一调度。作者创建了三个注解,并在核心模块中实现ClassScanner类进行包扫描,通过MappingHandler处理接口uri及方法映射。DispatcherServlet负责统一处理请求,通过反射机制调用接口并返回结果。文章还探讨了如何优化接口调度,提高性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录:

  • 自定义接口常用注解:
    @MyController @MyRequestMapping  @MyRequestParam
  • 包扫描
  • 实现接口统一调度: DispatcherServlet

1. 自定义接口常用注解

  • 在framework包下新建annotation包,新增3个注解
  • package com.mp.framework.annotation;
    
    import java.lang.annotation.*;
    
    @Documented
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyController {
    
    }
    
    package com.mp.framework.annotation;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyRequestMapping {
        String value();
    }
    
    package com.mp.framework.annotation;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target(ElementType.PARAMETER)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyRequestParam {
        String value();
    }
    

    2.包扫描

  • core下新建ClassScanner类

  • package com.mp.framework.core;
    
    import sun.net.www.protocol.file.FileURLConnection;
    
    import java.io.File;
    import java.io.IOException;
    import java.net.JarURLConnection;
    import java.net.URL;
    import java.net.URLConnection;
    import java.util.ArrayList;
    import java.util.Enumeration;
    import java.util.List;
    import java.util.jar.JarEntry;
    import java.util.jar.JarFile;
    
    /**
     * ClassName: ClassScanner
     * Function:  包扫描通过类加载器获取目录下的类列表
     * Date:      2020-05-07 10:42
     * author     mp
     * version    V1.0
     */
    public class ClassScanner {
        private List<Class<?>> classes;
        {
            classes = new ArrayList<>();
        }
        // 扫描指定包下的类列表
        public  List<Class<?>> doScanner(String packageName) throws IOException, ClassNotFoundException {
            String path = packageName.replace(".", "/");
            // 获取类加载器
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            Enumeration<URL> resources = classLoader.getResources(path);
            while (resources.hasMoreElements()){
                URL url = resources.nextElement();
                if (url.getProtocol().equalsIgnoreCase("file")) {
                    // todo 遍历所有的子类文件
                    File dir = new File(url.getPath());
                    for (File file: dir.listFiles()){
                        if (file.isDirectory()){
                            doScanner(new StringBuilder(packageName).append(".").append(file.getName()).toString());
                        }else {
                            classes.add(Class.forName(new StringBuilder(path.replace("/",".")).append(".").append(file.getName()).toString().replace(".class","")));
                        }
                    }
                }
            }
            return classes;
        }
    
       
    }
    

     (1) 使用类加载器  classLoader, 文件遍历class文件,包括递归遍历

  • (2)通过反射将扫描的class,做容器统一管理

  • 接口uri及方法等信息映射 MappingHandler

  • package com.mp.framework.handler;
    
    import com.mp.framework.beans.MyBeanFactory;
    import org.apache.naming.factory.BeanFactory;
    
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import java.io.IOException;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.Optional;
    import java.util.stream.Stream;
    
    /**
     * ClassName: MappingHandler
     * Function:  TODO
     * Date:      2020-05-07 14:12
     * author     mp
     * version    V1.0
     */
    public class MappingHandler {
        private String uri;
        private Method method;
        private Class<?> cls;
        private String[] args;
    
        public MappingHandler(String uri, Method method, Class<?> cls, String[] args) {
            this.uri = uri;
            this.method = method;
            this.cls = cls;
            this.args = args;
        }
    
        // 执行接口方法
        public boolean handle(ServletRequest req, ServletResponse res) throws IllegalAccessException, InstantiationException, InvocationTargetException, IOException {
            String reqUri = ((HttpServletRequest) req).getRequestURI();
            if (!this.uri.equals(reqUri)) return false;
    
            Object[] params = new Object[args.length];
            // 获取参数
            for (int i=0;i< args.length;i++){
                params[i] = req.getParameter(args[i]);  // 获取请求参数值
            }
    
            Object obj = this.cls.newInstance();
    
            // 执行方法,获取返回值
            Object response = this.method.invoke(obj, params);
            res.getWriter().write(Optional.ofNullable(response).map(r -> r.toString()).orElse(""));  // 针对无返回值类型的接口,需要做null处理
            return true;
    
        }
    }
    

    说明:(1)拦截servlet服务,获取请求的uri,参数值

  • (2) 通过反射机制,调用方法执行,并将执行结果返回

  •  这里,可能会有一个疑问? 怎么获取接口的地址uri、方法名及参数列表呢? 这就涉及到要去解析我们的自定义注解了,根据不同注解的作用来解析出不同的变量

  • 增加HandlerManager服务,

  •  

    package com.mp.framework.handler;
    
    import com.mp.framework.annotation.MyController;
    import com.mp.framework.annotation.MyRequestMapping;
    import com.mp.framework.annotation.MyRequestParam;
    import org.apache.commons.lang3.StringUtils;
    
    import java.lang.reflect.Method;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.function.Consumer;
    import java.util.stream.Collectors;
    import java.util.stream.Stream;
    
    /**
     * ClassName: HandlerManager
     * Function:  TODO
     * Date:      2020-05-07 13:13
     * author     mp
     * version    V1.0
     */
    public class HandlerManager {
        public static List<MappingHandler> list = new ArrayList<>();  // 用于统一管理接口uri映射
    
        public static void resolveMappingHandler(List<Class<?>> classes){
            classes.stream().filter(cls -> cls.isAnnotationPresent(MyController.class))
                    .forEach(controllerHandlerConsumer);
        }
    
        static Consumer<Class<?>> controllerHandlerConsumer = cls -> {
        
            // 反射,获取方法
            Method[] methods = cls.getDeclaredMethods();
    
            String finalTypeUri = typeUri;
            Stream.of(methods)
                    .filter(method -> method.isAnnotationPresent(MyRequestMapping.class))   // 只处理controller接口,@myRequestMapping 注解的方法
                    .forEach(method -> {
                        // 获取uri路径, 注解的值
                        String uri = method.getDeclaredAnnotation(MyRequestMapping.class).value();
                        
                        // 获取参数列表
                        List<String> params = Stream.of(method.getParameters())
                                .filter(parameter -> parameter.isAnnotationPresent(MyRequestParam.class))
                                .map(parameter -> parameter.getDeclaredAnnotation(MyRequestParam.class).value())
                                .collect(Collectors.toList());
    
                        MappingHandler mappingHandler = new MappingHandler(uri, method, cls, params.toArray(new String[params.size()]));
                        list.add(mappingHandler);
                    });
        };
    
    }
    

    说明:(1)这里使用一个集合,来存放所有的servlet映射

  • (2)通过 cls.isAnnotationPresent(MyController.class) 来过滤,只处理接口相关服务

  • (3)利用反射,获取类所有的方法,判断方法上是否有@MyRequestMapping注解,如果有,就代表是一个接口,所以需要获取注解的value值,即接口请求的uri

  • (4)参数列表获取: 利用反射,获取方法所有的参数 method.getParameters() , 遍历判断是否有@MyRequestParam注解,只要有,就代表是接口的一个请求参数,value值即为参数名 (需要根据这个参数名来获取传递的参数值)

  • (5)遍历完所有的接口后,将其统一管理起来.

  • 统一处理请求: DispatcherServlet

  •  

    package com.mp.framework.servlet;
    
    import com.mp.framework.handler.HandlerManager;
    
    import javax.servlet.*;
    import java.io.IOException;
    import java.lang.reflect.InvocationTargetException;
    
    /**
     * ClassName: DispatcherServlet
     * Function:  统一处理请求
     * Date:      2020-05-07 14:59
     * author     mp
     * version    V1.0
     */
    public class DispatcherServlet implements Servlet {
        @Override
        public void init(ServletConfig config) throws ServletException {
    
        }
    
        @Override
        public ServletConfig getServletConfig() {
            return null;
        }
    
        @Override
        public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
            HandlerManager.list.forEach(mh -> {   // 获取controller注解的方法列表? todo 这里可以优化,遍历去比对所有接口列表(性能低),uri一致就调用,后面实现精准调用.
                try {
                    mh.handle(req,res);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
        }
    
        @Override
        public String getServletInfo() {
            return null;
        }
    
        @Override
        public void destroy() {
    
        }
    }
    

    说明,需要在TomcatServer中注册 DispatcherServlet,拦截所有的请求;--这一点,性能低下,可以实现精准调用

  • 修改TomcatServer

  •  

    package com.mp.framework.web.server;
    
    import com.mp.framework.servlet.DispatcherServlet;
    import com.mp.framework.servlet.Servlet01;
    import com.mp.framework.servlet.Servlet02;
    import org.apache.catalina.Context;
    import org.apache.catalina.LifecycleException;
    import org.apache.catalina.core.StandardContext;
    import org.apache.catalina.startup.Tomcat;
    
    import javax.servlet.Servlet;
    
    /**
     * ClassName: TomcatServer
     * Function:  集成Tomcat服务器
     * Date:      2020-05-07 09:55
     * author     mp
     * version    V1.0
     */
    public class TomcatServerNew {
        private Tomcat tomcat;
        private String[] args;
    
        public TomcatServerNew(String[] args){
            this.args = args;
        }
    
        public void startServer() throws LifecycleException {
            // 实例化Tomcat
            tomcat = new Tomcat();
            tomcat.setPort(9999);
            tomcat.start();
    
            // 实例化context容器
            Context context = new StandardContext();
            context.setPath("");
            context.addLifecycleListener(new Tomcat.FixContextListener());
    
            Servlet dispatcherServlet = new DispatcherServlet();
            Tomcat.addServlet(context,"dispatcherServlet",dispatcherServlet).setAsyncSupported(true);
            // 添加URl映射
            context.addServletMappingDecoded("/","dispatcherServlet");
            tomcat.getHost().addChild(context);
    
            // 设置守护进程
            Thread thread = new Thread("tomcat-await-thread") {
                @Override
                public void run() {
                    super.run();
                    TomcatServerNew.this.tomcat.getServer().await();
                }
            };
            // 设置为非守护进程
            thread.setDaemon(false);
            thread.start();
        }
    }
    

    监听dispatcherServlet,拦截所有请求.

  • 修改MyApplication 服务,启动包扫描,旨在处理接口相关注解服务

  • package com.mp.framework.starter;
    
    import com.mp.framework.beans.MyBeanFactory;
    import com.mp.framework.core.ClassScanner;
    import com.mp.framework.handler.HandlerManager;
    import com.mp.framework.web.server.TomcatServer;
    import com.mp.framework.web.server.TomcatServerNew;
    import org.apache.catalina.LifecycleException;
    
    import java.io.IOException;
    import java.util.List;
    
    /**
     * ClassName: MyApplication
     * Function:  TODO
     * Date:      2020-05-07 09:44
     * author     mp
     * version    V1.0
     */
    public class MyApplication {
        public static void run(Class<?> cls,String[] args) throws LifecycleException, IOException, ClassNotFoundException {
            System.out.println("hello my0-spring application!!!");
    
            // 实例化Tomcat服务
    //        TomcatServer tomcatServer = new TomcatServer(args);
            TomcatServerNew tomcatServer = new TomcatServerNew(args);
            tomcatServer.startServer();
    
            // 添加获取类列表和反射调用
            List<Class<?>> list = new ClassScanner().doScanner(cls.getPackage().getName());
            list.stream().forEach(c -> System.out.println(c.getName()));
            // 筛选controller 接口,处理接口映射
            HandlerManager.resolveMappingHandler(list);
    
        }
    }
    

     说明:(1)doScanner 扫描包及其子包

  • (2)HandlerManager.resolveMappingHandler(list);  用于统计接口调度做准备

  • (3)这里使用新的TomcatServerNew服务,只监听 dispatcherServlet

  • demo中新建TestController接口

  •  

    package com.mp.demo.controller;
    
    import com.mp.demo.service.Service01;
    import com.mp.framework.annotation.MyController;
    import com.mp.framework.annotation.MyRequestMapping;
    import com.mp.framework.annotation.MyRequestParam;
    import com.mp.framework.beans.MyAutowired;
    
    /**
     * ClassName: TestController
     * Function:  TODO
     * Date:      2020-05-07 15:04
     * author     mp
     * version    V1.0
     */
    @MyController
    public class TestController {
    
        @MyRequestMapping("/test")
        public String test01(@MyRequestParam("name") String name){
            System.out.println("do it test01...");
          
            return "this name: "+name;
        }
    
        @MyRequestMapping("/test02")
        public void test02(@MyRequestParam("addr") String addr){
            System.out.println("do it test02: "+ addr);
        }
    }
    

     

  • 启动服务,访问接口 http://localhost:9999/test?name=mp 成功访问.

  • 可能有人会问? 在springboot框架中,在接口上经常指定RequestMapping地址,那我们这里能否也加上呢,这个当然可以,想一想原理也很简单,就是在遍历类的注解上增加@MyRequestMapping扫描,获取values值,到时候作为方法uri的前缀就可以解决这个问题了

  • 实现: 1. @MyRequestMapping 增加target范围

  •  

    package com.mp.framework.annotation;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target({ElementType.METHOD,ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyRequestMapping {
        String value();
    }
    

    HandlerManager 服务中增加类上@MyRequestMapping 注解解析服务

  •   

    package com.mp.framework.handler;
    
    import com.mp.framework.annotation.MyController;
    import com.mp.framework.annotation.MyRequestMapping;
    import com.mp.framework.annotation.MyRequestParam;
    import org.apache.commons.lang3.StringUtils;
    
    import java.lang.reflect.Method;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.function.Consumer;
    import java.util.stream.Collectors;
    import java.util.stream.Stream;
    
    /**
     * ClassName: HandlerManager
     * Function:  TODO
     * Date:      2020-05-07 13:13
     * author     mp
     * version    V1.0
     */
    public class HandlerManager {
        public static List<MappingHandler> list = new ArrayList<>();  // 用于统一管理接口uri映射
    
        public static void resolveMappingHandler(List<Class<?>> classes){
            classes.stream().filter(cls -> cls.isAnnotationPresent(MyController.class))
                    .forEach(controllerHandlerConsumer);
        }
    
        static Consumer<Class<?>> controllerHandlerConsumer = cls -> {
            // 判断controller上是否有 MyRequestMapping 注解,需要加到uri上
            String typeUri = "";
            if (cls.isAnnotationPresent(MyRequestMapping.class)){
                typeUri = cls.getDeclaredAnnotation(MyRequestMapping.class).value();
            }
    
            // 反射,获取方法
            Method[] methods = cls.getDeclaredMethods();
    
            String finalTypeUri = typeUri;
            Stream.of(methods)
                    .filter(method -> method.isAnnotationPresent(MyRequestMapping.class))   // 只处理controller接口,@myRequestMapping 注解的方法
                    .forEach(method -> {
                        // 获取uri路径, 注解的值
                        String uri = method.getDeclaredAnnotation(MyRequestMapping.class).value();
                        if (StringUtils.isNotEmpty(finalTypeUri)){
                            uri = new StringBuilder(finalTypeUri).append(uri).toString();
                        }
                        // 获取参数列表
                        List<String> params = Stream.of(method.getParameters())
                                .filter(parameter -> parameter.isAnnotationPresent(MyRequestParam.class))
                                .map(parameter -> parameter.getDeclaredAnnotation(MyRequestParam.class).value())
                                .collect(Collectors.toList());
    
                        MappingHandler mappingHandler = new MappingHandler(uri, method, cls, params.toArray(new String[params.size()]));
                        list.add(mappingHandler);
                    });
        };
    
    }
    
    TestController类上增加 @MyRequestMapping("/v1")
  •  

    package com.mp.demo.controller;
    
    import com.mp.demo.service.Service01;
    import com.mp.framework.annotation.MyController;
    import com.mp.framework.annotation.MyRequestMapping;
    import com.mp.framework.annotation.MyRequestParam;
    import com.mp.framework.beans.MyAutowired;
    
    /**
     * ClassName: TestController
     * Function:  TODO
     * Date:      2020-05-07 15:04
     * author     mp
     * version    V1.0
     */
    @MyController
    @MyRequestMapping("/v1")
    public class TestController {
        @MyAutowired
        Service01 service01;
    
        @MyRequestMapping("/test")
        public String test01(@MyRequestParam("name") String name){
            System.out.println("do it test01...");
            return service01.method01(name);
        }
    
        @MyRequestMapping("/test02")
        public void test02(@MyRequestParam("addr") String addr){
            System.out.println("do it test02: "+ addr);
        }
    }
    

    重启服务,访问http://localhost:9999/v1/test?name=mp 正常.

  • 2020-05-07 优化接口同一调度DispatcherServlet,将uri映射转存Map存储,这样在分配接口时,就不用做list遍历,直接从map中取出对应的MappingHandler来处理,接口量大时,性能高些

  • package com.mp.framework.handler;
    
    import com.mp.framework.annotation.MyController;
    import com.mp.framework.annotation.MyRequestMapping;
    import com.mp.framework.annotation.MyRequestParam;
    import org.apache.commons.lang3.StringUtils;
    
    import java.lang.reflect.Method;
    import java.util.List;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.function.Consumer;
    import java.util.stream.Collectors;
    import java.util.stream.Stream;
    
    /**
     * ClassName: HandlerManager
     * Function:  TODO
     * Date:      2020-05-07 13:13
     * author     mp
     * version    V1.0
     */
    public class HandlerManager {
        public static Map<String,MappingHandler> map = new ConcurrentHashMap<>();  // 用于统一管理接口uri映射
    
        public static void resolveMappingHandler(List<Class<?>> classes){
            classes.stream().filter(cls -> cls.isAnnotationPresent(MyController.class))
                    .forEach(controllerHandlerConsumer);
        }
    
        static Consumer<Class<?>> controllerHandlerConsumer = cls -> {
            // 判断controller上是否有 MyRequestMapping 注解,需要加到uri上
            String typeUri = "";
            if (cls.isAnnotationPresent(MyRequestMapping.class)){
                typeUri = cls.getDeclaredAnnotation(MyRequestMapping.class).value();
            }
    
            // 反射,获取方法
            Method[] methods = cls.getDeclaredMethods();
    
            String finalTypeUri = typeUri;
            Stream.of(methods)
                    .filter(method -> method.isAnnotationPresent(MyRequestMapping.class))   // 只处理controller接口,@myRequestMapping 注解的方法
                    .forEach(method -> {
                        // 获取uri路径, 注解的值
                        String uri = method.getDeclaredAnnotation(MyRequestMapping.class).value();
                        if (StringUtils.isNotEmpty(finalTypeUri)){
                            uri = new StringBuilder(finalTypeUri).append(uri).toString();
                        }
                        // 获取参数列表
                        List<String> params = Stream.of(method.getParameters())
                                .filter(parameter -> parameter.isAnnotationPresent(MyRequestParam.class))
                                .map(parameter -> parameter.getDeclaredAnnotation(MyRequestParam.class).value())
                                .collect(Collectors.toList());
    
                        MappingHandler mappingHandler = new MappingHandler(uri, method, cls, params.toArray(new String[params.size()]));
                        map.put(uri,mappingHandler);
                    });
        };
    
    }
    
    package com.mp.framework.servlet;
    
    import com.mp.framework.handler.HandlerManager;
    import com.mp.framework.handler.MappingHandler;
    
    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import java.io.IOException;
    import java.lang.reflect.InvocationTargetException;
    
    /**
     * ClassName: DispatcherServlet
     * Function:  统一处理请求
     * Date:      2020-05-07 14:59
     * author     mp
     * version    V1.0
     */
    public class DispatcherServlet implements Servlet {
        @Override
        public void init(ServletConfig config) throws ServletException {
    
        }
    
        @Override
        public ServletConfig getServletConfig() {
            return null;
        }
    
        @Override
        public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
            /*HandlerManager.list.forEach(mh -> {   // 获取controller注解的方法列表? todo 这里可以优化,遍历去比对所有接口列表(性能低),uri一致就调用,后面实现精准调用.
                try {
                    mh.handle(req,res);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });*/
            String reqUri = ((HttpServletRequest) req).getRequestURI();
            try {
                HandlerManager.map.get(reqUri).handle(req, res);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    
        @Override
        public String getServletInfo() {
            return null;
        }
    
        @Override
        public void destroy() {
    
        }
    }
    

    改造完毕...

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值