闭关修炼180天 -- 手写SpringMVC框架(迷你版)

SpringMvc知识须知

MVC设计模式

  • Model(模型):模型包含业务模型和数据模型,数据模型⽤于封装数据,业务模型⽤于处理业 务。
  • View(视图): 通常指的就是我们的 jsp 或者 html。作⽤⼀般就是展示数据的。通常视图是依据 模型数据创建的。
  • Controller(控制器): 是应⽤程序中处理⽤户交互的部分。作⽤⼀般就是处理程序逻辑的。

springMvc请求执行流程

  • ⽤户发送请求⾄前端控制器DispatcherServlet
  • DispatcherServlet收到请求调⽤HandlerMapping处理器映射器
  • 处理器映射器根据请求Url找到具体的Handler(后端控制器),⽣成处理器对象及处理器拦截 器(如果 有则⽣成)⼀并返回DispatcherServlet
  • DispatcherServlet调⽤HandlerAdapter处理器适配器去调⽤Handler
  • 处理器适配器执⾏Handler
  • Handler执⾏完成给处理器适配器返回ModelAndView
  • 处理器适配器向前端控制器返回 ModelAndView,ModelAndView 是SpringMVC 框架的⼀个 底层对 象,包括 Model 和 View
  • 前端控制器请求视图解析器去进⾏视图解析,根据逻辑视图名来解析真正的视图。
  • 视图解析器向前端控制器返回View
  • 前端控制器进⾏视图渲染,就是将模型数据(在 ModelAndView 对象中)填充到 request 域
  • 前端控制器向⽤户响应结果

 

 

 

SpringMVC九大核心组件

  • 1.HandlerMapping(处理器映射器):找到请求响应的处理器Handler和Interceptor
  • 2.HandlerAdapter(处理器适配器):要让固定的Servlet处理方法调用Handler来处理
  • 3.HandlerExceptionResolver(异常处理器),用于处理Handler产生的异常情况
  • 4.ViewResolver(视图解析器),将Sting类型的视图名解析为View类型的视图
  • 5.RequestToViewNameTranslator,在请求域中获取ViewName,当Handler处理完后,没有设置View,那便从这个组件中从请求中获取ViewName
  • 6.LocaleResolver,区域化国际化组件
  • 7.ThemeResolver 主题解析器
  • 8.MultipartResolver多文件上传解析器
  • 9.FlashMapManager,用于重定向时的参数传递

 

自定义SpringMVC框架

大致流程

  • 引进javax.servlet坐标,创建servlet包,创建HttpServlet的实现类MyServlet
  • 创建annotation包,创建四个注解:@MyController,@MyRequestMapping,@MyService,@MyAutowired
  • 重写HttpServlet的几个方法init(),doPost(),doGet()
    • 在init()方法中加载解析springMvc.xml文件
    • 在init()方法中完成注解的功能增强。
    • 在init()方法中初始化ioc容器
    • 在init()方法中完成依赖的注入
    • 构造一个处理器映射器HandlerMapping,将我们处理好的url和Method建立映射关系
    • 处理权限关系

项目结构

 

 

 

代码实现

1.pom文件主要内容

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

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>


        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.9</version>
        </dependency>
    </dependencies>



    <build>
        <plugins>
            <!--编译插件定义编译细节-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                    <encoding>utf-8</encoding>
                    <!--告诉编译器,编译的时候记录下形参的真实名称-->
                    <compilerArgs>
                        <arg>-parameters</arg>
                    </compilerArgs>
                </configuration>
            </plugin>


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

2.web.xml 以及 springmvc.properties

<!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>MyServlet</servlet-name>
    <servlet-class>com.zae.frame.servlet.MyServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>springmvc.properties</param-value>
    </init-param>
  </servlet>

  <servlet-mapping>
    <servlet-name>MyServlet</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>

</web-app>
        
scanPackage=com.zae

3.五个注解的定义

import java.lang.annotation.*;

@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAutowired {
    String value() default "";
}
import java.lang.annotation.*;

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyController {
    String value() default "";
}
import java.lang.annotation.*;

@Documented
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRequestMapping {
    String value() default "";
}
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyService {
    String value() default "";
}
/**
 * 1.当未添加该注解时,表明该方法不加入权限控制,所有请求均可访问
 * 2.当该注解放置在某个类上,则表明该类下的所有方法都可以被该注解指定的用户访问
 * 3.当该注解放置在某个方法时,则以该方法上的用户指定为准
 */
@Target({ElementType.TYPE,ElementType.METHOD})
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Security {
    //配置用户名称,指定的用户可以访问
    String [] value() default {"/"};
}

4.handler实体的定义

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

/**
 * 封装映射关系
 */
public class Handler {
    //controller
    private Object controller;
    //方法
    private Method method;
    //url的正则表达式
    private Pattern pattern;
    //存储参数字段以及其的入参位置
    private Map<String,Integer> handlerMapping;
    //存储有权限访问的用户
    private String [] securityUser;

    public Handler(Object controller, Method method, Pattern pattern) {
        this.controller = controller;
        this.method = method;
        this.pattern = pattern;
        this.handlerMapping = new HashMap<String, Integer>();
    }

    public String[] getSecurityUser() {
        return securityUser;
    }

    public void setSecurityUser(String[] securityUser) {
        this.securityUser = securityUser;
    }

    public Object getController() {
        return controller;
    }

    public void setController(Object controller) {
        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 Map<String, Integer> getHandlerMapping() {
        return handlerMapping;
    }

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

5.MyServlet核心类的实现

import com.zae.frame.annotation.*;
import com.zae.frame.pojo.Handler;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.ServletConfig;
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.Matcher;
import java.util.regex.Pattern;


public class MyServlet extends HttpServlet {
    //配置文件信息:springmvc.xml
    Properties properties = new Properties();
    //存放扫描包下的全限类名
    List<String> classList = new ArrayList<String>();
    //ioc容器,存放实例对象
    Map<String,Object> beanMap = new HashMap<String,Object>();
    //存放Handler的集合
    List<Handler> handlerList = new ArrayList<Handler>();

    @Override
    public void init(ServletConfig config){
        //1.加载配置文件:springmvc.xml
        String path = config.getInitParameter("contextConfigLocation");
        doLoanConfig(path);

        //2.扫描包下所有的类
        doScan(properties.getProperty("scanPackage"));
        //3.初始化bean对象,基于注解,加入ioc容器
        doInstance();
        //4.完成依赖的注入@MyAutowired
        doAutowired();
        //5.构建好一个HandlerMapping,完成url和method之间的映射关系
        initHandleMapping();
        //6.处理用户权限问题
        doSecurity();

        System.out.println("迷你版SpringMvc初始化完成....");
    }

    /**
     * 完成Controller层的用户访问权限问题
     */
    private void doSecurity() {
        if (handlerList.size() == 0){return;}

        for(Handler handler:handlerList){
            Class<?> aClass = handler.getController().getClass();
            String [] securityUserArr = null;
            //1.当controller类上有@Security注解时,则改类中所有的方法都加入权限控制,以类上的@Security里面的value为准
            if(aClass.isAnnotationPresent(Security.class)){
                Security annotation = aClass.getAnnotation(Security.class);
                securityUserArr = annotation.value();
            }
            //2.当方法上存在@Security注解时,则以方法的@Security中的value为准,覆盖类上声明的那个权限注解
            Method method = handler.getMethod();
            if(method.isAnnotationPresent(Security.class)){
                Security annotation = method.getAnnotation(Security.class);
                securityUserArr = annotation.value();
            }
            //3.如果无论controller类还是其中的方法都没有加Security注解,此时securityUserArr==null,那么可以任意访问,不会拦截,
            //4.如果controller层加了Security注解,但是没有重新定义value值或者value值声明为{"/"},那么此时securityUserArr=={"/"},会拦截所有用户的请求
            handler.setSecurityUser(securityUserArr);
        }
    }

    /**
     * 完成url与方法之间的映射
     */
    private void initHandleMapping() {
        if(beanMap.size() == 0){return;}
        for(Map.Entry<String,Object> map:beanMap.entrySet()){
            Class<?> aClass = map.getValue().getClass();
            String baseUrl = "";
            //当controller类上有MyRequestMapping注解时,基础url为value值
            if(aClass.isAnnotationPresent(MyRequestMapping.class)){
                MyRequestMapping annotation = aClass.getAnnotation(MyRequestMapping.class);
                baseUrl = annotation.value();
            }
            //获取Controller的所有方法
            Method[] methods = aClass.getMethods();
            for (Method method : methods) {
                //当方法上有MyRequestMapping注解时,获取其value值
                if(method.isAnnotationPresent(MyRequestMapping.class)){
                    MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
                    //请求url
                    String url = baseUrl+annotation.value();
                    Pattern compile = Pattern.compile(url);
                    Handler handler = new Handler(map.getValue(),method,compile);

                    //计算方法的参数位置信息
                    Parameter[] parameters = method.getParameters();
                    for (int i = 0; i < parameters.length; i++) {
                        //当参数为HttpServletRequest和HttpServletResponse时,key存储的是参数的类型名
                        if(parameters[i].getType() == HttpServletRequest.class || parameters[i].getType() == HttpServletResponse.class){
                            handler.getHandlerMapping().put(parameters[i].getType().getSimpleName(),i);
                        }else{
                            //当其他情况的时候,key存储的是参数名称
                            handler.getHandlerMapping().put(parameters[i].getName(),i);
                        }
                    }
                    //将handler存储起来
                    handlerList.add(handler);
                }
            }
        }
    }



    /**
     * 依赖注入
     */
    private void doAutowired() {
        if(beanMap.size()==0){return;}
        for(Map.Entry<String,Object> mapEntry:beanMap.entrySet()){
            String key = mapEntry.getKey();
            Object value = mapEntry.getValue();
            Class<?> aClass = value.getClass();
            //获取该类下所有的属性
            Field[] declaredFields = aClass.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                //判断属性上是否存在MyAutowired注解
                if(declaredField.isAnnotationPresent(MyAutowired.class)){
                    MyAutowired annotation = declaredField.getAnnotation(MyAutowired.class);
                    //获取注解里面的value值
                    String name = annotation.value();
                    //设置暴力访问
                    declaredField.setAccessible(true);
                    if("".equals(name.trim())){
                        //当注解的value值为空时,获取属性类型的全限类名,使用类型注入依赖
                        name = declaredField.getType().getName();
                    }
                    try {
                        //给该属性赋值
                        declaredField.set(value,beanMap.get(name));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
            //依赖赋值结束后,重新加入map集合中
            beanMap.put(key,value);
        }
    }

    /**
     * 基于注解完成ioc的初始化
     */
    private void doInstance() {
        if(classList.size()==0){return;}

        for (String className : classList) {

            try {
                //根据全限类名获取类对象
                Class<?> aClass = Class.forName(className);
                if(aClass.isAnnotationPresent(MyService.class)){
                    MyService annotation = aClass.getAnnotation(MyService.class);
                    String value = annotation.value();
                    Object beanObj = aClass.newInstance();
                    if("".equals(value)){
                        //当service注解为空时,使用首字母小写的类名
                        value = firstToLower(aClass.getSimpleName());

                        beanMap.put(value,beanObj);
                    }else{
                        //当service的value不为空时,使用注解里面的value值
                        beanMap.put(value,beanObj);
                    }

                    //面向接口开发,以接口全限类名作为id放入容器(针对service层存在接口)
                    Class<?>[] interfaces = aClass.getInterfaces();
                    if(interfaces!=null){
                        for (Class<?> anInterface : interfaces) {
                            beanMap.put(anInterface.getName(),aClass.newInstance());
                        }
                    }

                }else if(aClass.isAnnotationPresent(MyController.class)){
                    //MyController注解的类使用首字母小写的类名
                    Object beanObj = aClass.newInstance();
                    beanMap.put(firstToLower(aClass.getSimpleName()),beanObj);
                }else{
                    continue;
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private String firstToLower(String source){
        if(source==null){return null;}
        char[] chars = source.toCharArray();
        if(chars[0]>='A' && chars[0]<='Z'){
            chars[0] += 32;
        }
        return String.valueOf(chars);
    }
    /**
     * 扫描包下所有的类
     * @param scanPackage
     */
    private void doScan(String scanPackage) {
        String scanPackagePath =Thread.currentThread().getContextClassLoader().getResource("").getPath()+scanPackage.replaceAll("\\.","/");
        File fileScan = new File(scanPackagePath);
        File[] files = fileScan.listFiles();
        for (File file : files) {
            if(file.isDirectory()){
                //如果是文件夹,就继续递归
                doScan(scanPackage+"."+file.getName());

            }else if(file.getName().endsWith(".class")){
               String className = scanPackage+"."+file.getName().replaceAll(".class","");
               classList.add(className);
            }
        }
    }

    /**
     * 加载springmvc.properties配置文件
     * @param path
     */
    private void doLoanConfig(String path) {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(path);
        try {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        //设置中文编码格式
        resp.setContentType("text/html;charset=UTF-8");
        doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        Handler handler = getHandler(req);
        if(handler == null){
            resp.getWriter().write("404 No fount");
            return;
        }
        //检查用户权限问题
        if(!checkSecurity(handler,req,resp)){return;}

        //参数绑定工作
        Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();

        //根据上述长度创建一个新的数组,参数传递时反射调用
        Object[] objParam = new Object[parameterTypes.length];

        //为了向参数数组中塞值,保证形参和方法的参数顺序一致
        Map<String, String[]> parameterMap = req.getParameterMap();
        //拿到方法的入参顺序
        Map<String, Integer> handlerMapping = handler.getHandlerMapping();
        for(Map.Entry<String,String[]> map:parameterMap.entrySet()){
            String join = StringUtils.join(map.getValue(), ",");
            String key = map.getKey();
            //如果字段名字匹配上了,则进行赋值操作
            if(parameterMap.containsKey(key)){
                objParam[handlerMapping.get(key)] = join;
            }
        }

        //处理HttpServletRequest和HttpServletResponse
        int reqIndex = handlerMapping.get(HttpServletRequest.class.getSimpleName());

        int respIndex = handlerMapping.get(HttpServletResponse.class.getSimpleName());

        objParam[reqIndex] = req;

        objParam[respIndex] = resp;

        Method method =handler.getMethod();
        Object controllerObj = handler.getController();
        try {
            //进行方法调用
            method.invoke(controllerObj,objParam);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }


    }

    /**
     * 检查用户权限问题
     * @param req
     * @return
     */
    private Boolean checkSecurity(Handler handler,HttpServletRequest req,HttpServletResponse resp) throws IOException {
        //当securityUser == null时,没有加权限控制,任意请求都能访问
        if(handler.getSecurityUser() == null){
            return true;
        }

        //当securityUser == {/},任意请求都不能访问
        if("/".equals(handler.getSecurityUser()[0])){
            resp.getWriter().write("您无权访问!请联系管理员");
            return false;
        }

        //前端的参数,访问的用户
        String username = req.getParameter("username");
        //当securityUser存在值时,则进行校验
        List<String> userList = Arrays.asList(handler.getSecurityUser());
        if(userList.contains(username)){
            return true;
        }else{
            resp.getWriter().write("您无权访问!请联系管理员");
            return false;
        }
    }

    /**
     * 根据请求获取Handler对象
     * @param req
     * @return
     */
    private Handler getHandler(HttpServletRequest req){
        if(handlerList.isEmpty()){return null;}

        String url = req.getRequestURI();

        for (Handler handler : handlerList) {
            //判断url和正则是否匹配
            Matcher matcher = handler.getPattern().matcher(url);
            if(!matcher.matches()){continue;}
            return handler;
        }
        return null;
    }
}

6.使用端Service以及Controller(测试使用,不再关联数据库了)

public interface TestService {

    String start(String username);
}
import com.zae.demo.service.TestService;
import com.zae.frame.annotation.MyService;

@MyService
public class TestServiceImpl implements TestService {
    @Override
    public String start(String username) {
        System.out.println("访问的用户为:"+username);
        return username;
    }
}
import com.zae.demo.service.TestService;
import com.zae.frame.annotation.MyAutowired;
import com.zae.frame.annotation.MyController;
import com.zae.frame.annotation.MyRequestMapping;
import com.zae.frame.annotation.Security;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Security({"wangsulong","xuezhiqian"})
@MyController
@MyRequestMapping("/demoSecurity")
public class TestSecurityController {

    @MyAutowired
    private TestService testService;

    /**
     * 方法中有@Security,则覆盖类定义的权限注解
     * @param request
     * @param response
     * @param username
     * @return
     */
    @Security({"xusong"})
    @MyRequestMapping("/startSecurityOne")
    public String startSecurityOne(HttpServletRequest request, HttpServletResponse response,String username){
        return testService.start(username);
    }

    /**
     * 方法中没有@Security,则使用类里面的权限注解定义的
     * @param request
     * @param response
     * @param username
     * @return
     */
    @MyRequestMapping("/startSecurityTwo")
    public String startSecurityTwo(HttpServletRequest request, HttpServletResponse response,String username){
        return testService.start(username);
    }

    /**
     * @Security中的value没有设定值,则拦截所欲
     * @param request
     * @param response
     * @param username
     * @return
     */
    @Security
    @MyRequestMapping("/startSecurityThree")
    public String startSecurityThree(HttpServletRequest request, HttpServletResponse response,String username){
        return testService.start(username);
    }
}

自定义SpringMvc框架至此结束

 文章知识点输出来源:拉勾教育Java高薪训练营

 2021第一天,我是帝莘,期待和你的技术交流以及思想碰撞

posted @ 2021-01-01 17:36  帝莘  阅读(179)  评论(1编辑  收藏  举报