• 使用Java元注解和反射实现简单MVC框架


    Springmvc的核心是DispatcherServlet来进行各种请求的拦截,进而进行后续的各种转发处理。流程图如下:

      说明:客户端发出一个http请求给web服务器,web服务器对http请求进行解析,如果匹配DispatcherServlet的请求映射路径(在web.xml中指定),web容器将请求转交给DispatcherServlet.DipatcherServlet接收到这个请求之后将根据请求的信息(包括URL、Http方法、请求报文头和请求参数Cookie等)以及HandlerMapping的配置找到处理请求的处理器(Handler)。DispatcherServlet根据HandlerMapping找到对应的Handler,将处理权交给Handler(Handler将具体的处理进行封装),再由具体的HandlerAdapter对Handler进行具体的调用。Handler对数据处理完成以后将返回一个ModelAndView()对象给DispatcherServlet。Handler返回的ModelAndView()只是一个逻辑视图并不是一个正式的视图,DispatcherSevlet通过 ViewResolver将逻辑视图转化为真正的视图View。Dispatcher通过model解析出ModelAndView()中的参数进行解析最终展现出完整的view并返回给客户端。

    以下基于java元注解和反射等知识来实现简单MVC框架。

    1、Servlet

    Servlet 3.0 之前使用web.xml文件进行配置,例如:

      <servlet>
            <serlvet-name>myServlet</servlet-name>
            <servlet-calss>MyServlet的类路径</servlet-class>
        </servlet>
        
        <servlet-mapping>
          <serlvet-name>myServlet</servlet-name>
          <url-pattern>/servlet/myServlet</url-pattern>
        </servlet-mapping>

    Servlet 3.0 后可以基于注解来处理Servlet

    @WebServlet(name = "dispatcherServlet", urlPatterns = "/*", loadOnStartup = 1,
                initParams = {@WebInitParam(name = "base-package", value = "com.kinson.myspring")})

    name:servlet名

    urlPatterns:url匹配模式

    loadOnStartup:标记容器是否在启动的时候就加载这个servlet(实例化并调用其init()方法),它的值必须是一个整数,表示servlet应该被载入的顺序.

    1.   当值为0或者大于0时,表示容器在应用启动时就加载并初始化这个servlet
    2.   当值小于0或者没有指定时,则表示容器在该servlet被选择时才会去加载
    3.   正数的值越小,该servlet的优先级越高,应用启动时就越先加载
    4.   当值相同时,容器就会自己选择顺序来加载 

    initParams:初始化参数,此处表示定义了一个名为base-package,值为com.kinson.myspring的WebInitParam对象,可以通过ServletConfig的getInitParameter("base-package");方法获取对应的值。

    2、实现

    2.1 工程目录结构

    相关代码说明:

    1. 在 annotation 包下,我将提供自定义的注解,为了方便理解,会与 Spring MVC 保持一致。JDK 元注解介绍

    2. 为了模拟 Spring MVC 的方法调用链,我这里提供 Controller/Service/Dao 层进行测试。

    3. 提供自定义的 DispatcherServlet 来完成核心逻辑处理。

    具体代码实现:

    2.2 pom引入servlet依赖

        <!--项目依赖-->
        <dependencies>
            <!-- servlet 依赖-->
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <version>${servlet.version}</version>
            </dependency>
            
        </dependencies>

    2.3 Annotation

    以Controller为例,其他的注解类似:

    //用于类、接口、枚举enum
    @Target(ElementType.TYPE)
    //生命周期为运行时
    @Retention(RetentionPolicy.RUNTIME)
    //javadoc
    @Documented
    public @interface Controller {
    
        /**
         * 作用于该注解的一个value属性
         * @return
         */
        String value();
    }

    2.4 请求拦截类DispatcherServlet :

    定义相关全局存储变量:

    // @WebServlet 以前我们定义一个 Servlet ,需要在 web.xml 中去配置,不过在 Servlet 3.0 后出现了基于注解的 Servlet 。
    @WebServlet(name = "dispatcherServlet", urlPatterns = "/*", loadOnStartup = 1,
            initParams = {@WebInitParam(name = "base-package", value = "com.kinson.myspring")})
    public class DispatcherServlet extends HttpServlet {
        /**
         * 扫描的包
         */
        private String basePackage = "";
    
        /**
         * 基包下面所有的带包路径权限定类名
         */
        private List<String> packageNames = new ArrayList<String>();
    
        /**
         * 注解实例化 格式为注解上的名称:注解实例化对象
         */
        private Map<String, Object> instanceMap = new HashMap<String, Object>();
    
        /**
         * 包路径权限定类名称:注解上的名称
         */
        private Map<String, String> nameMap = new HashMap<String, String>();
    
        /**
         * Url地址和方法的映射关系:注解上的名称
         */
        private Map<String, Method> urlMethodMap = new HashMap<String, Method>();
    
        /**
         * Method和权限定类名的映射关系,用于通过Method找到该方法的对象利用反射执行
         */
        private Map<Method, String> methodPackageMap = new HashMap<Method, String>();
    }

    初始化方法init:

      /**
         * 初始化
         * 1、扫描基包下的类,得到信息 A。
         * 2、对于 @Controller/@Service/@Repository 注解而言,我们需要拿到对应的名称,并初始化它们修饰的类,形成映射关系 B。
         * 3、扫描类中的字段,如果发现有 @Qualifier 的话,我们需要完成注入。
         * 4、扫描 @RequestMapping,完成 URL 到某一个 Controller 的某一个方法上的映射关系 C。
         *
         * @param config
         */
        @Override
        public void init(ServletConfig config) {
            System.out.println("开始初始化。。。。。。");
    
            //通过初始化参数直接将需要扫描的基包路径传入
            basePackage = config.getInitParameter("base-package");
    
            try {
                //扫描基包得到全部的带包路径权限定类名
                scanBasePackage(basePackage);
                //把代用注解的类实例化方如Map中,key为注解上的名称
                instance(packageNames);
                //IOC注入
                springIOC();
                //完成Url地址与方法的映射关系
                handleUrlMethodMap();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
    
            System.out.println("初始化结束。。。。。。");
        }

    方法scanBasePackage通过初始化参数直接将需要扫描的基包路径传入:

      /**
         * 通过初始化参数直接将需要扫描的基包路径传入
         *
         * @param basePackage 基包路径
         */
        private void scanBasePackage(String basePackage) {
            //加载类资源路径
            URL url = this.getClass().getClassLoader()
                    .getResource(basePackage.replaceAll("\.", "/"));
    
            File basePackageFile = new File(url.getPath());
            System.out.println("scan:" + basePackageFile);
            File[] childFiles = basePackageFile.listFiles();
            for (File file : childFiles) {
                //目录递归扫描
                if (file.isDirectory()) {
                    scanBasePackage(basePackage + "." + file.getName());
                } else if (file.isFile()) {
                    //Controller.class====Controller,即去掉.class
                    System.out.println(">>>>>>>>>>> " + file.getName() + "====" + file.getName().split("\.")[0]);
                    packageNames.add(basePackage + "." + file.getName().split("\.")[0]);
                }
            }
        }

    方法instance把代用注解的类实例化方如Map中,key为注解上的名称:

    /**
         * 把代用注解的类实例化方如Map中,key为注解上的名称
         *
         * @param packageNames 包路径名集合
         */
        private void instance(List<String> packageNames) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
            if (packageNames.size() < 1) {
                return;
            }
    
            for (String packageName : packageNames) {
                //根据包路径获取Class对象
                Class<?> clazz = Class.forName(packageName);
                //Controller注解处理
                if (clazz.isAnnotationPresent(Controller.class)) {
                    Controller controller = (Controller) clazz.getAnnotation(Controller.class);
                    String controllerName = controller.value();
    
                    instanceMap.put(controllerName, clazz.newInstance());
                    nameMap.put(packageName, controllerName);
    
                    System.out.println("Controller :" + packageName + ", value :" + controllerName);
                } else if (clazz.isAnnotationPresent(Service.class)) {
                    //Service注解处理
                    Service service = (Service) clazz.getAnnotation(Service.class);
                    String serviceName = service.value();
    
                    instanceMap.put(serviceName, clazz.newInstance());
                    nameMap.put(packageName, serviceName);
    
                    System.out.println("Service :" + packageName + ", value :" + serviceName);
                } else if (clazz.isAnnotationPresent(Repository.class)) {
                    //Repository注解处理
                    Repository repository = clazz.getAnnotation(Repository.class);
                    String repositoryName = repository.value();
    
                    instanceMap.put(repositoryName, clazz.newInstance());
                    nameMap.put(packageName, repositoryName);
                    System.out.println("Repository :" + packageName + ", value :" + repositoryName);
                }
            }
        }

    方法springIOC注入:

    /**
         * IOC注入
         */
        private void springIOC() throws IllegalAccessException {
            for (Map.Entry<String, Object> instanceEntry : instanceMap.entrySet()) {
                //获取当前对象的所有字段
                Field[] declaredFields = instanceEntry.getValue().getClass().getDeclaredFields();
    
                for (Field field : declaredFields) {
                    //字段上是否有Qualifier注解
                    if (field.isAnnotationPresent(Qualifier.class)) {
                        Qualifier qualifier = field.getAnnotation(Qualifier.class);
                        String qualifierName = qualifier.value();
    
                        //设置当前的字段为可访问
                        field.setAccessible(Boolean.TRUE);
                        //设置当前字段
                        field.set(instanceEntry.getValue(), instanceMap.get(qualifierName));
    
                        System.out.println("==========" + field);
                    }
                }
            }
        }

    方法handleUrlMethodMap处理Url地址与方法的映射关系:

    /**
         * Url地址与方法的映射关系
         *
         * @throws ClassNotFoundException
         */
        private void handleUrlMethodMap() throws ClassNotFoundException {
            if (packageNames.size() < 1) {
                return;
            }
    
            for (String packageName : packageNames) {
                //根据包路径获取Class对象
                Class clazz = Class.forName(packageName);
    
                //当前类是否有Controller注解
                if (clazz.isAnnotationPresent(Controller.class)) {
                    //获取当前Controller类的所有方法
                    Method[] methods = clazz.getMethods();
                    //拼接访问URI
                    StringBuffer baseUrl = new StringBuffer();
    
                    //当前Controller是否有RequestMapping注解
                    if (clazz.isAnnotationPresent(RequestMapping.class)) {
                        RequestMapping requestMapping = (RequestMapping) clazz.getAnnotation(RequestMapping.class);
                        //XxxController类上的requestMapping值
                        baseUrl.append(requestMapping.value());
                    }
    
                    for (Method method : methods) {
                        if (method.isAnnotationPresent(RequestMapping.class)) {
                            RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
                            //XxxController类上的响应方法上的requestMapping值
                            baseUrl.append(requestMapping.value());
    
                            //URL 提取出来,映射到 Controller 的 Method 上。
                            System.out.println("baseUrl : " + baseUrl.toString());
                            urlMethodMap.put(baseUrl.toString(), method);
                            methodPackageMap.put(method, packageName);
                        }
                    }
                }
            }
        }

    doGet/doPost方法处理拦截请求的业务逻辑:

       @Override
        public void doGet(HttpServletRequest req, HttpServletResponse resp) {
            doPost(req, resp);
        }
    
        @Override
        public void doPost(HttpServletRequest req, HttpServletResponse resp) {
    
            //获取请求URI,eg:/user/hello
            final String uri = req.getRequestURI();
            final String contextPath = req.getContextPath();
            final String path = uri.replaceAll(contextPath, "");
    
            //提取出 URL,通过 URL 映射到Method 上,然后通过反射的方式进行调用即可。
            Method method = urlMethodMap.get(path);
            if (null != method) {
                //通过方法获取方法所在的包路径
                String packageName = methodPackageMap.get(method);
                //通过包路径获取注解上的名称
                String controllerName = nameMap.get(packageName);
                //通过注解名称获取对应的实例对象
                UserController userController = (UserController) instanceMap.get(controllerName);
    
                try {
                    //设置方法可访问
                    method.setAccessible(Boolean.TRUE);
                    //利用反射进行方法调用
                    method.invoke(userController);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }

    测试UserController类:

    @Controller("userController")
    @RequestMapping("/user")
    public class UserController {
    
        @Qualifier("userServiceImpl")
        private UserService userService;
    
        @RequestMapping(value = "/hello")
        public String hello() {
            System.out.println("UserController.hello");
            return "UserController.hello";
        }
    }

    测试UserService接口:

    public interface UserService {
    
        void hello();
    }

    测试UserServiceImpl接口:

    @Service("userServiceImpl")
    public class UserServiceImpl implements UserService {
    
        @Override
        public void hello() {
            System.out.println("hello, myspring");
        }
    }

    3、测试

    配置tomcat运行项目,此处我用的是idea,具体配置:

    选择本地安装的tomcat:

    设置相关内容:

    点击右下角的Fix按钮选择部署包:

    配置好之后运行,在浏览器输入测试url:

    idea控制台打印了内容:

    到此,一个简单的MVC框架就ok了。

    Github源码参照

  • 相关阅读:
    小白的linux笔记3:对外联通——开通ssh和ftp和smb共享
    小白的linux笔记2:关于进程的基本操作
    小白的linux笔记1:CentOS 8 安装与设置
    Python+Flask+MysqL的web技术建站过程
    管理信息系统 第三部分 作业
    数据迁移
    模型分离(选做)
    密码保护
    实现搜索功能
    完成个人中心—导航标签
  • 原文地址:https://www.cnblogs.com/kingsonfu/p/10635894.html
Copyright © 2020-2023  润新知