zoukankan      html  css  js  c++  java
  • 闭关修炼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第一天,我是帝莘,期待和你的技术交流以及思想碰撞

  • 相关阅读:
    django框架——十二
    django框架——十一
    请简述一下你所了解的数据源控件有哪些
    在ASP.NET中,<%= %>和<%# %>有什么区别
    请解释ASP.NET中的web页面与其隐藏类之间的关系
    什么是viewstate,能否禁用?是否所用控件都可以禁用
    WEB控件及HTML服务端控件能否调用客户端方法?如果能,请解释如何调用
    静态类和静态方法的好处
    请写出在ASP.NET中常用的几种页面间传值的方法,并说出它们的特点。
    连接数据库主要有哪几个对象
  • 原文地址:https://www.cnblogs.com/zaevn00001/p/14220443.html
Copyright © 2011-2022 走看看