手写MVC框架

1.SpringMvc执行大致原理

tomcat加载web.xml,前端控制器DispatchServlet加载指定配置文件springmvc.xml
包扫描,扫描注解Controller,Service,RequestMapping,Autowire
Ioc容器就是要进行相应Bean初始化以及依赖维护关系
SpringMvc相关组件初始化,建立url和method之间的映射关系
等待请求进来,处理请求....

2.手写Mvc之注解开发

2.0.配置文件

  • pom.xml
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.example</groupId>
  <artifactId>mvc</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>mvc Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <!--servlet-->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
    <!--commoms-lang-->
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.12.0</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <!--编译插件,防止jdk在编译时将参数识别为args1,而不使用自己原本的参数名称-->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
        <configuration>
          <encoding>utf-8</encoding>
          <compilerArgs>
            <arg>-parameters</arg>
          </compilerArgs>
        </configuration>
      </plugin>

      <plugin>
        <groupId>org.apache.tomcat.maven</groupId>
        <artifactId>tomcat7-maven-plugin</artifactId>
        <configuration>
          <port>8080</port>
          <path>/</path>
        </configuration>
        <version>2.2</version>
      </plugin>
    </plugins>
  </build>


</project>

  • web.xml
<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>
  
  <servlet>
    <servlet-name>MvcDispatcherServlet</servlet-name>
    <servlet-class>com.edu.mvcframework.servlet.MvcDispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>mvc.properties</param-value>
    </init-param>
  </servlet>

  <servlet-mapping>
    <servlet-name>MvcDispatcherServlet</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>
</web-app>
  • mvc.properties
scanPackage=com.demo

2.1.自定义注解类

  • MvcController
package com.edu.mvcframework.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MvcController {
    String value() default "";
}

  • MvcService
package com.edu.mvcframework.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MvcService {
    String value() default "";
}

  • MvcRequestMapping
package com.edu.mvcframework.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MvcRequestMapping {
    String value() default "";
}

  • MvcAutowire
package com.edu.mvcframework.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MvcAutowire {
    String value() default "";
}

  • Security
package com.edu.mvcframework.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Security {

    String[] value();
}

2.2.自定义前端控制器

  • MvcDispatcherServlet
package com.edu.mvcframework.servlet;

import com.edu.mvcframework.annotation.*;
import com.edu.mvcframework.pojo.Handle;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Pattern;

public class MvcDispatcherServlet extends HttpServlet {

    private Properties properties = new Properties();

    // 定义一个容器,存储扫描到的所有类的全路径名称
    private Set<String> classNames = new HashSet<>();

    // ioc容器
    private Map<String,Object> iocMap = new HashMap<>();

    // 存储url和method的映射关系容器
    private Map<String, Handle> handlerMap = new HashMap<>();

    // 标识已处理过的类
    private Set<String> handledClasses = new HashSet<>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        String mvcPropFile = config.getInitParameter("contextConfigLocation");
       try {
           // 1.加载自定义mvc框架配置文件
           doLoadProperties(mvcPropFile);

           // 2.扫描包,获取所有类
           doScan(properties.getProperty("scanPackage"));

           // 3.实例化对象,将含有自定义注解的类存储到ioc容器当中
           doInstance();

           // 4.处理类之间的依赖关系
           doAutowire();

           // 5.处理url和method之间的映射关系,进行存储
           doInitHandleMapping();

           // 等待请求到来
       }catch (Exception e){
           e.printStackTrace();
       }
    }

    private void doInitHandleMapping() throws ClassNotFoundException {
        if(iocMap.size() == 0){return;}
        for (Map.Entry<String, Object> entry : iocMap.entrySet()) {

            Class<?> aClass = entry.getValue().getClass();
            // 已经处理过映射的不在处理
            if (handledClasses.contains(aClass.getName())){continue;}

            // 非controller类,不进行处理
            if (!aClass.isAnnotationPresent(MvcController.class)){continue;}

            if (aClass.isAnnotationPresent(MvcRequestMapping.class)){
                MvcRequestMapping baseUrlAnno = aClass.getAnnotation(MvcRequestMapping.class);
                String baseUrl = baseUrlAnno.value();  // /demo

                // 获取所有的方法,判断是否含有requestMapping注解
                Method[] declaredMethods = aClass.getDeclaredMethods();
                for (Method declaredMethod : declaredMethods) {

                    // 方法没有MvcRequestMapping标识,不做处理
                    if (!declaredMethod.isAnnotationPresent(MvcRequestMapping.class)) {
                        continue;
                    }
                    MvcRequestMapping methodUrlAnno = declaredMethod.getAnnotation(MvcRequestMapping.class);
                    String methodUrl = methodUrlAnno.value();  // /getName
                    String url = baseUrl + methodUrl;   // /demo/getName

                    // 把method所有信息及url封装为⼀个Handler
                    Handle handle = new Handle(declaredMethod,Pattern.compile(url),entry.getValue());

                    // 参数顺序设置
                    Parameter[] parameters = declaredMethod.getParameters();
                    for (int i = 0; i < parameters.length; i++) {
                        if (parameters[i].getType() == HttpServletRequest.class
                                || parameters[i].getType() == HttpServletResponse.class ){
                            // 如果是request和response对象,
                            // 那么参数名称写HttpServletRequest和HttpServletResponse
                            handle.getParametersIndex().put(parameters[i].getType().getSimpleName(),i);
                        }else {
                            handle.getParametersIndex().put(parameters[i].getName(),i);
                        }
                    }

                    handlerMap.put(url,handle);

                }
                handledClasses.add(aClass.getName());
            }

        }
        handledClasses.clear();
    }

    private void doAutowire() throws IllegalAccessException {
        if(iocMap.size() == 0){return;}

        for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
            Class<?> aClass = entry.getValue().getClass();
            // ioc容器中存在根据接口,以及本身类名存储的实例化对象。即一个实例化对象的key可能有多个
            // 已经处理过不在处理
            if (handledClasses.contains(aClass.getName())){
                continue;
            }
            // 获取所有字段,判断是否有autowire注解,有则进行依赖
            Field[] declaredFields = aClass.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                if (!declaredField.isAnnotationPresent(MvcAutowire.class)) {
                    continue;
                }
                MvcAutowire autowireAnno = declaredField.getAnnotation(MvcAutowire.class);
                String beanName = autowireAnno.value();
                if("".equals(beanName.trim())){
                    // 没有配置bean的Id,则按照接口注入
                    beanName = declaredField.getType().getName();
                }
                declaredField.setAccessible(true);
                declaredField.set(entry.getValue(),iocMap.get(beanName));
            }
            // 处理完标记下已经处理过
            handledClasses.add(aClass.getName());
        }

        handledClasses.clear();
    }

    private void doInstance() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        if(classNames.size() == 0){return;}

        for (String className : classNames) {

            Class<?> aClass = Class.forName(className);

            // 如果是Controller注解,对名称不做过多处理
            if (aClass.isAnnotationPresent(MvcController.class)){
                iocMap.put(lowerFirst(aClass.getSimpleName()),aClass.newInstance());
            }else if(aClass.isAnnotationPresent(MvcService.class)){
                // 如果是service注解,需要取值存放,没有需要默认类名存放
                MvcService serviceAnno = aClass.getAnnotation(MvcService.class);
                String beanName = serviceAnno.value();

                Object newInstance = aClass.newInstance();
                if("".equals(beanName.trim())){
                    beanName = lowerFirst(aClass.getSimpleName());
                }
                iocMap.put(beanName,newInstance);

                //如果实现了接口,需要按接口名称再存放一份,以接⼝的全限定类名作为id放⼊
                Class<?>[] interfaces = aClass.getInterfaces();
                for (Class<?> anInterface : interfaces) {
                    iocMap.put(anInterface.getName(),newInstance);
                }
            }else {
                continue;
            }

        }

    }

    private String lowerFirst(String str){
        char[] chars = str.toCharArray();
        if('A' <= chars[0] && chars[0] <= 'Z'){
            chars[0] += 32;
        }
        return String.valueOf(chars);
    }

    private void doScan(String basePackage) {
        // 需要将.替换成/
        String pack = this.getClass().getClassLoader().getResource("").getPath() +
                basePackage.replaceAll("\\.", "/"); // com.demo  com/demo
        File packs = new File(pack);
        File[] files = packs.listFiles();
        for (File file : files) {
            if(file.isDirectory()){
               doScan(basePackage +"."+file.getName());
            }else if (file.getName().endsWith(".class")){
                    String className = basePackage +"."+ file.getName().replaceAll(".class","");
                    classNames.add(className);
            }else {
                continue;
            }
        }


    }

    private void doLoadProperties(String mvcPropFile) throws IOException {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(mvcPropFile);
        properties.load(resourceAsStream);
    }

    @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(); // /demo/getName
        // 根据uri获取到能够处理当前请求的hanlder   Handle handle = getHandle(requestURI);
        Handle handle = handlerMap.get(requestURI);
        if (handle == null){
            resp.getWriter().write("404 not found");
            return;
        }

        // 权限校验
        String userName = req.getParameter("username");
        if(!doSecurity(handle,userName)){
            resp.setContentType("text/html;charset=UTF-8");
            resp.getWriter().write(userName + "无权限访问...");
            return;
        }

        Method method = handle.getMethod();
        // 参数绑定
        Map<String, Integer> parametersIndex = handle.getParametersIndex();
        Object[] params = new Object[parametersIndex.size()];
        // 参数为HttpServletRequest,HttpServletResponse
        int requestIndex =parametersIndex.get(HttpServletRequest.class.getSimpleName());
        params[requestIndex] = req;
        int responseIndex =parametersIndex.get(HttpServletResponse.class.getSimpleName());
        params[responseIndex] = resp;

        // 遍历request中所有参数 (填充除了request,response之外的参数)
        Map<String, String[]> parameterMap = req.getParameterMap();
        for (Map.Entry<String, String[]> stringEntry : parameterMap.entrySet()) {
            // name=1&name=2这种 name[1,2]
            String paramVal = StringUtils.join(stringEntry.getValue(), ",");  // 如同 1,2
            if (parametersIndex.keySet().contains(stringEntry.getKey())){
                Integer index = parametersIndex.get(stringEntry.getKey());
                // 把前台传递过来的参数值填充到对应的位置去
                params[index] = paramVal;
            }
        }


        try {
            method.invoke(handle.getController(),params);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }


    }

    private boolean doSecurity(Handle handle, String userName) {
        boolean controllerPrivilege = true;
        boolean methodPrivilege = true;
        // 获取controller上的@Sercurity注解
        Class<?> controllerClass = handle.getController().getClass();
       if (controllerClass.isAnnotationPresent(Security.class)){
           Security securityAnno = controllerClass.getAnnotation(Security.class);
           String[] securityAnnoArray = securityAnno.value();
           if (Objects.nonNull(securityAnnoArray) && securityAnnoArray.length != 0){
               if (!Arrays.asList(securityAnnoArray).contains(userName)){
                   controllerPrivilege = false;
               }
           }

       }
        // 获取方法上的注解
        if (handle.getMethod().isAnnotationPresent(Security.class)){
            Security securityAnno = handle.getMethod().getAnnotation(Security.class);
            String[] securityAnnoArray = securityAnno.value();
            if (Objects.nonNull(securityAnnoArray) && securityAnnoArray.length != 0){
                if (!Arrays.asList(securityAnnoArray).contains(userName)){
                    methodPrivilege = false;
                }
            }
        }

        // 只要controller或者method上有@sercurity注解,就获取权限名单,如果用户在名单中,就放行
        return controllerPrivilege && methodPrivilege;
    }

    private Handle getHandle(String uri){
        for (Map.Entry<String, Handle> stringHandleEntry : handlerMap.entrySet()) {
            Handle handle = stringHandleEntry.getValue();
            if (! handle.getPattern().matcher(uri).matches()){
                continue;
            }else {
                return handle;
            }
        }
        return null;
    }
}

  • 前端控制器的init方法会在第一次访问servlet时进行调用.在init逻辑中还增添了简单的权限校验.

2.3.pojo类Handle类

package com.edu.mvcframework.pojo;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

public class Handle {

    private Method method;
    private Pattern pattern;
    private Object controller;
    private Map<String,Integer> parametersIndex = new HashMap<>();// 参数对应位置

    public Handle(Method method, Pattern pattern, Object controller) {
        this.method = method;
        this.pattern = pattern;
        this.controller = controller;
    }

    public Method getMethod() {
        return method;
    }

    public void setMethod(Method method) {
        this.method = method;
    }

    public Pattern getPattern() {
        return pattern;
    }

    public void setPattern(Pattern pattern) {
        this.pattern = pattern;
    }

    public Object getController() {
        return controller;
    }

    public void setController(Object controller) {
        this.controller = controller;
    }

    public Map<String, Integer> getParametersIndex() {
        return parametersIndex;
    }

    public void setParametersIndex(Map<String, Integer> parametersIndex) {
        this.parametersIndex = parametersIndex;
    }
}

3.测试自定义框架

3.1.controller层

  • DemoController
package com.demo.controller;

import com.demo.service.IDemoService;
import com.edu.mvcframework.annotation.MvcAutowire;
import com.edu.mvcframework.annotation.MvcController;
import com.edu.mvcframework.annotation.MvcRequestMapping;
import com.edu.mvcframework.annotation.Security;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@MvcController
@MvcRequestMapping("/demo")
@Security({"zhangsan","lisi","wangwu"})
public class DemoController {

    @MvcAutowire
    private IDemoService demoService;

    @MvcRequestMapping("/security01")
    public void security01(HttpServletRequest request, HttpServletResponse response,String username) throws IOException {
        response.setContentType("text/html;charset=UTF-8");
        response.getWriter().write(username +"访问security01成功....");
    }

    @MvcRequestMapping("/security02")
    @Security({"zhangsan"})
    public void security02(HttpServletRequest request, HttpServletResponse response,String username) throws IOException {
        response.setContentType("text/html;charset=UTF-8");
        response.getWriter().write(username + "访问security02成功....");
    }

    @MvcRequestMapping("/getName")
    public void getName(HttpServletRequest request, HttpServletResponse response,String name){
        demoService.get(name);
    }
}

3.2.service层

  • IDemoService
package com.demo.service;

public interface IDemoService {

    public void get(String name);
}

  • DemoServiceImpl
package com.demo.service.impl;

import com.demo.service.IDemoService;
import com.edu.mvcframework.annotation.MvcService;

@MvcService
public class DemoServiceImpl implements IDemoService {

    @Override
    public void get(String name) {
        System.out.println("进入到了service实现类...name是:{"+name+"}");
    }
}

3.3.启动测试

debug启动tomcat插件
访问:http://localhost:8080/demo/getName?name=zhangsan&name=lis

文章内容输出来源:拉勾教育Java高薪训练营

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值