SpringMVC架构模拟

这次来学习一下SpringMVC的源码.

对于常见的项目架构模式,比如大名鼎鼎的SSM(SpringMVC,Spring,Mybatis)框架.

  • SpringMVC ->web层(Controller层)
  • Spring ->service层
  • mybatis ->dao层

从SpringMVC层面上讲,他的构成如下:

  • Model ->数据
  • View ->视图
  • Controller ->业务
经过上面的分层,使得数据,视图(展示效果),业务逻辑进行分离,每一层的变化可以不影响其他层,增加程序的可维护性和可扩展性。

在这里插入图片描述

  1. 浏览器发出用户请求,处于web.xml中的dispacherServlet前端控制器进行接收,此时这个前端控制器并不处理请求.而是将用户发送的url请求转发到HandleMapping处理器映射器

  2. 第二步:HandlerMapping根据请求路径找到相对应的HandlerAdapter处理器适配器(处理器适配器就是那些拦截器或者是controller)

  3. 第三步:HandlerAdapter处理器适配器,可以处理一些功能请求,返回一个ModelAndView对象(包括模型数据/逻辑视图名)

  4. 第四步:ViewResolver视图解析器,先根据 ModelAndView中设置的view解析具体视图

  5. 第五步:然后再将Model模型中的数据渲染到View中

下面我们实际在项目中进行操作

一丶创建一个带有web.xml的maven项目
二丶首先自己写一个类继承HttpServlet类并重写它的doGet,doPost方法
package com.spring.mvc.config;

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

/**
 * @Author: XiaoZhe
 * @Description:
 * @Date: Created in 17:39 2019/12/16
 */
public class MyDispatchServlet extends HttpServlet{
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("这是调用了doGet方法");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("这是调用了doPost方法");
    }
}
三丶修改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>
  <!--注册servlet-->
  <servlet>
    <!--自己继承了HttpServlet类的名字-->
    <servlet-name>httpServletTest</servlet-name>
    <!--自己继承了HttpServlet类的所在路径-->
    <servlet-class>com.spring.mvc.servlet.MyDispatchServlet</servlet-class>
  </servlet>
  <!--映射servlet-->
  <servlet-mapping>
    <!--上面自定义的servlet-name-->
    <servlet-name>httpServletTest</servlet-name>
    <!--拦截路径/* -->
    <url-pattern>/*</url-pattern>
  </servlet-mapping>
</web-app>

四丶 启动项目,在地址栏输入项目地址并回车

可以看到控制台打印输出了我们定义的话

这是调用了doGet方法
这是调用了doGet方法

五丶创建几个注解@Controller,@RequestMapping

用过SpringMVC框架的人都知道在类上打了@Controller注解的才能被认作是一个Controller,而打了@RequestMapping才能被请求映射。

@MyController
package com.spring.mvc.annotation;

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

/**
 * @Author: ZhengZhe
 * @Description: 作用于class上的功能类似于Spring的@Controller注解
 * @Date: Created in 10:34 2019/12/17
 */
@Target(ElementType.TYPE)//标识此注解只能作用在类上面
@Retention(RetentionPolicy.RUNTIME)//标识此注解一直存活,可被反射获取
public @interface MyController {
}
@MyRequestMapping
package com.spring.mvc.annotation;

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

/**
 * @Author: ZhengZhe
 * @Description: 作用于class或者method上的功能类似于Spring的@RequestMapping注解
 * @Date: Created in 10:38 2019/12/17
 */
@Target({ElementType.TYPE,ElementType.METHOD})//标识此注解只能作用在类或者方法上面
@Retention(RetentionPolicy.RUNTIME)//标识此注解一直存活,可被反射获取
public @interface MyRequestMapping {
    String value();//用来存储对应的url , 网络请求路径
}
六丶DispatchServlet

DispatchServlet在MVC引导着非常强大的作用,网络中的请求传到DispatchServlet中,由DispatchServlet进行截取分析并传到对应的由@Controller和@RequestMapping注解的类或方法中,使得网路请求能正确的请求到对应的资源上.

下面我们自定义一个DispatchServlet实现他所实现的功能
首先看一下源码中MVC做了什么

protected void initStrategies(ApplicationContext context) {
        this.initMultipartResolver(context);
        this.initLocaleResolver(context);
        this.initThemeResolver(context);
        this.initHandlerMappings(context);
        this.initHandlerAdapters(context);
        this.initHandlerExceptionResolvers(context);
        this.initRequestToViewNameTranslator(context);
        this.initViewResolvers(context);
        this.initFlashMapManager(context);
    }

从上面我们可以看到初始化方法的参数是ApplicationContext,这个是IOC的初始化容器,我之前的博客中解析过IOC的源码,不懂的可以去里面解读.
initStrategies方法的目的就是从容器中获取已经解析出来的bean资源,并获取其带有@Controller和@RequestMapping注解的bean资源.

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        if (this.logger.isDebugEnabled()) {
            String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
            this.logger.debug("DispatcherServlet with name '" + this.getServletName() + "'" + resumed + " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
        }

        Map<String, Object> attributesSnapshot = null;
        if (WebUtils.isIncludeRequest(request)) {
            attributesSnapshot = new HashMap();
            Enumeration attrNames = request.getAttributeNames();

            label108:
            while(true) {
                String attrName;
                do {
                    if (!attrNames.hasMoreElements()) {
                        break label108;
                    }

                    attrName = (String)attrNames.nextElement();
                } while(!this.cleanupAfterInclude && !attrName.startsWith("org.springframework.web.servlet"));

                attributesSnapshot.put(attrName, request.getAttribute(attrName));
            }
        }

        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());
        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
        request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.getThemeSource());
        FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
        if (inputFlashMap != null) {
            request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
        }

        request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
        request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);

        try {
            this.doDispatch(request, response);
        } finally {
            if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
                this.restoreAttributesAfterInclude(request, attributesSnapshot);
            }

        }

    }

doService方法其实目的就是解析用户的请求路径,根据请求路径找到对应类和方法,使用反射调用.

七丶MyDispatchServlet(自定义前端控制器)

我们自己写代码来实现对应的init方法和Service方法的功能.

package com.spring.mvc.servlet;

import com.spring.mvc.annotation.MyController;
import com.spring.mvc.annotation.MyRequestMapping;

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.lang.reflect.Method;
import java.net.URL;
import java.util.Enumeration;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * @Author: ZhengZhe
 * @Description:
 * @Date: Created in 10:56 2019/12/17
 */
public class MyDispatchServlet extends HttpServlet{
    //我们定义两个集合去存储扫描到的带有@MyController 和 @MyRequestMapping注解的类或者方法
    //存放 被@MyRequestMapping注解修饰的类或者方法
    private ConcurrentHashMap<String,Method> MyMethodsCollection = new ConcurrentHashMap();
    //存放 被@MyController注解修饰的类
    private ConcurrentHashMap<String,Object> MyControllerCollection = new ConcurrentHashMap();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        MyInit(req,resp);

        MyService(req,resp);

    }

    /**
     * 作用是 : 根据配置的包名,扫描所有包含@MyController注解修饰的类和@MyRequestMapping注解修饰的类或者方法
     *并将对应的 beanName放入上面定义的 集合中
     * @param req
     * @param resp
     */
    private void MyInit(HttpServletRequest req, HttpServletResponse resp) {
        //在源码中,有IOC实现了容器的扫描和初始化,MVC中只是直接拿出来,但是这个方法中我们就不通过容器去获取了
        //直接扫描并且存入上面的集合中就可以了
        String basePackage ="com.spring.mvc";
        //根据传入的包路径,扫描包,此时只是将该包下所有的文件资源存入集合中,但是并没有筛选加了@MyController和
        //@RequestMapping注解的类,即扫描所有.class字节码对象并保存起来
        ConcurrentHashMap<String, Class<?>> scannerClass = scannerClass(basePackage);

        //下面直接进行筛选@MyController和@RequestMapping注解的类
        Set<Map.Entry<String, Class<?>>> entrySet = scannerClass.entrySet();
        for (Map.Entry<String, Class<?>> entry : entrySet) {
            //获取key : 类名称
            String className = entry.getKey();
            //获取value : 对应的.class字节码对象
            Class<?> clazz = entry.getValue();
            //定义MyRequestMapping的url
            String classUrl = "";
            try {
                //该类是否标记了MyController注解
                if (clazz.isAnnotationPresent(MyController.class)){
                    //该类是否标记了MyRequestMapping注解
                    if (clazz.isAnnotationPresent(MyRequestMapping.class)){
                        //如果该类被MyRequestMapping注解所标识,获取其属性url值
                        MyRequestMapping requestMapping = clazz.getAnnotation(MyRequestMapping.class);
                        classUrl = requestMapping.value();
                    }
                    //将被标识了MyController注解的类存入集合中
                    MyControllerCollection.put("className",clazz.newInstance());
                    //判断该类中的方法是否标识了@MyRequestMapping注解,如果标识了存入MyMethodsCollection集合中
                    //获取该类下的方法
                    Method[] methods = clazz.getMethods();//获取该类中的方法数组
                    //遍历该数组
                    for (Method method : methods) {
                        //判断是否被@MyRequestMapping注解标识
                        if (method.isAnnotationPresent(MyRequestMapping.class)){
                            //已被@MyRequestMapping注解标识
                            //获取其url
                            MyRequestMapping myRequestMapping = method.getAnnotation(MyRequestMapping.class);
                            //获取method上的url
                            String methodUrl = myRequestMapping.value();
                            //拼接两端MyController和method的url
                            MyMethodsCollection.put(classUrl+methodUrl,method);
                        }
                    }


                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    //根据传入的包路径,进行bean资源的获取,一般可以在xml中设置包路径.但是我们直接给出即可(简单)
    private static ConcurrentHashMap<String, Class<?>> scannerClass(String basePackage) {
        ConcurrentHashMap<String, Class<?>> result = new ConcurrentHashMap<>();
        //把com.spring.mvc 换成com/spring/mvc再类加载器读取文件
        String basePath = basePackage.replaceAll("\\.", "/");
        try {
            //得到com/spring/mvc的绝对地址 /D:xxxxx/com/spring/mvc
            String rootPath = MyDispatchServlet.class.getClassLoader().getResource(basePath).getPath();
            //只留com/ming/mvc 目的为了后续拼接成一个全限定名
            if (rootPath != null) {
                rootPath = rootPath.substring(rootPath.indexOf(basePath));
            }
            Enumeration<URL> enumeration = MyDispatchServlet.class.getClassLoader().getResources(basePath);
            while (enumeration.hasMoreElements()) {
                URL url = enumeration.nextElement();
                if (url.getProtocol().equals("file")) {//如果是个文件
                    File file = new File(url.getPath().substring(1));
                    scannerFile(file, rootPath, result);
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;

    }

    //递归扫描文件
    private static void scannerFile(File folder, String rootPath, ConcurrentHashMap<String, Class<?>> classes) {
        try {
            File[] files = folder.listFiles();
            for (int i = 0; files != null && i < files.length; i++) {
                File file = files[i];
                if (file.isDirectory()) {
                    scannerFile(file, rootPath + file.getName() + "/", classes);
                } else {
                    if (file.getName().endsWith(".class")) {
                        String className = (rootPath + file.getName()).replaceAll("/", ".");
                        className = className.substring(0, className.indexOf(".class"));//去掉扩展名得到全限定名
                        //Map容器存储全限定名和Class
                        classes.put(className, Class.forName(className));
                    }
                }

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



    /**
     * 作用是 : 解析用户的请求路径,,根据请求路径找到对应的类和方法,并使用反射调用其方法
     * @param req
     * @param resp
     */
    private void MyService(HttpServletRequest req, HttpServletResponse resp) {
        //在MyInit中我们已将被@MyController和@MyRequestMapping注解标识的类或者方法存入对应的集合中了;
        //下面我们需要将网络请求中的url和我们容器中初始化好的url进行匹配,如果匹配成功,那么直接执行此方法
        //返回除去host(域名或者ip)部分的路径(包含)
        String requestURI = req.getRequestURI();//类似test/test
        //返回工程名部分,如果工程映射为/,此处返回则为空 (工程名即项目名)
        String contextPath = req.getContextPath();//类似test
        //获取实际除 ip,端口,项目名外的请求路径
        //如web.xml中的servlet拦截路径设置的为/* 采用下面的方法,如果采用的是/*.do或者/*.action类似的后缀,需要把后面的也去掉
        String requestMappingPath = requestURI.substring(contextPath.length());
        //通过截取到的实际的请求url为key获取对应的方法
        Method method = MyMethodsCollection.get(requestMappingPath);
        try {
            if (method == null){
                //此时就是大名鼎鼎的404 了~
                //直接返回404
                resp.sendError(404);
                return;
            }
            //存在,那么直接执行
            //获取方法所对应的的class<?>字节码文件
            Class<?> declaringClass = method.getDeclaringClass();
            //下面按照源码来说还需要去判断是否是单例等操作,我们直接省去
            method.invoke(declaringClass.newInstance());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("这是调用了doPost方法");
    }
}

八丶创建ControllerTest测试类
package com.spring.mvc.controller;

import com.spring.mvc.annotation.MyController;
import com.spring.mvc.annotation.MyRequestMapping;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author: ZhengZhe
 * @Description:
 * @Date: Created in 15:07 2019/12/17
 */
@MyController
@MyRequestMapping("/hello")
public class ControllerTest {


    @MyRequestMapping("/world")
    public void helloworld(){
        System.out.println("自定义MVC测试成功~ ,现在时间是"+System.currentTimeMillis());
    }
}

查看控制台输出:
信息: At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
[2019-12-17 03:33:26,276] Artifact mvc:war exploded: Artifact is deployed successfully
[2019-12-17 03:33:26,276] Artifact mvc:war exploded: Deploy took 565 milliseconds
自定义MVC测试成功~ ,现在时间是1576568012163
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 好的,下面是一个基于Spring MVC框架的小程序支付接口的示例代码: 1. 配置文件: 在Spring的配置文件中,需要配置微信支付相关的参数,包括商户号、AppID、商户密钥等信息。可以将这些参数放在properties文件中,通过Spring的属性注入来获取。 ``` <!-- 微信支付配置 --> <util:properties id="wxpayConfig" location="classpath:wxpay.properties"/> <!-- 微信支付服务 --> <bean id="wxpayService" class="com.example.wxpay.WxPayService"> <property name="mchId" value="${wxpayConfig.mchId}"/> <property name="appId" value="${wxpayConfig.appId}"/> <property name="mchKey" value="${wxpayConfig.mchKey}"/> </bean> <!-- Spring MVC 配置 --> <mvc:annotation-driven/> <context:component-scan base-package="com.example.controller"/> ``` 2. Controller: 在Controller中,需要编写处理支付请求的方法。该方法接收支付订单信息,调用微信支付API生成预支付订单,并将生成的预支付订单信息返回给前端。在用户确认支付后,再调用微信支付API完成支付。 ``` @Controller @RequestMapping("/wxpay") public class WxPayController { @Autowired private WxPayService wxpayService; @RequestMapping("/createOrder") @ResponseBody public Map<String, String> createOrder(@RequestBody Map<String, Object> order) throws Exception { String body = (String) order.get("body"); String outTradeNo = (String) order.get("out_trade_no"); int totalFee = (int) order.get("total_fee"); Map<String, String> params = new HashMap<>(); params.put("body", body); params.put("out_trade_no", outTradeNo); params.put("total_fee", String.valueOf(totalFee)); params.put("trade_type", "JSAPI"); params.put("openid", "用户的openid"); Map<String, String> result = wxpayService.createOrder(params); Map<String, String> response = new HashMap<>(); response.put("appId", wxpayService.getAppId()); response.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000)); response.put("nonceStr", result.get("nonce_str")); response.put("package", "prepay_id=" + result.get("prepay_id")); response.put("signType", "MD5"); response.put("paySign", wxpayService.generatePaySign(response)); return response; } @RequestMapping("/notify") @ResponseBody public String notify(@RequestBody String xmlData) throws Exception { Map<String, String> result = wxpayService.parsePayResult(xmlData); // 处理支付结果 return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>"; } } ``` 3. WxPayService: WxPayService是一个封装了微信支付API的服务类,用于生成预支付订单、处理支付结果等操作。 ``` @Service public class WxPayService { private String mchId; private String appId; private String mchKey; // 省略构造函数和Getter/Setter方法 public Map<String, String> createOrder(Map<String, String> params) throws Exception { SortedMap<String ### 回答2: 要编写一个基于Spring MVC架构的小程序支付接口,我们可以按照以下步骤进行: 1. 首先,我们需要搭建一个Spring MVC项目。在pom.xml文件中添加相关的依赖,如Spring MVCSpring Boot、MyBatis等。 2. 接下来,我们需要配置Spring MVC。创建一个配置类,使用@EnableWebMvc注解开启Spring MVC功能,并配置视图解析器、静态资源访问、拦截器等。 3. 编写一个支付Controller,用于接收小程序请求并处理支付逻辑。我们可以使用@RequestMapping注解来定义支付接口的URL地址和请求方式。例如,可以定义一个支付接口为/pay,请求方式为POST。 4. 在Controller中,我们可以通过@RequestParam注解来获取小程序发来的参数,例如订单号、商品描述、金额等。 5. 在支付接口中,我们可以使用第三方支付平台的SDK来发起支付请求。例如,可以使用支付宝SDK或微信支付SDK。根据所选支付平台的具体文档,我们可以编写相应的代码,调用支付API,将支付参数传递给支付平台,并获取支付结果。 6. 处理支付结果。根据支付平台的返回结果,我们可以判断支付是否成功,并进行相应的处理。例如,可以更新订单状态、发送支付成功通知等。 7. 最后,我们可以编写一个简单的测试页面,用于模拟小程序发送支付请求。在页面中,我们可以填写订单号、商品描述、金额等,并通过Ajax方式调用支付接口。 这样,一个基于Spring MVC架构的小程序支付接口就完成了。我们可以通过扫描小程序的支付码或调用小程序的支付API,实现支付功能。同时,通过合理的异常处理和日志记录,我们可以提高支付接口的可靠性和可维护性。 ### 回答3: 在基于Spring MVC架构的小程序支付接口中,我们可以使用以下步骤来实现: 1. 首先,我们需要在Spring MVC配置文件中配置相关的bean,包括必要的依赖关系和初始化参数。 2. 接下来,我们需要编写一个控制器类,用于处理来自小程序的支付请求。可以使用@RequestMapping注解来映射URL路径和请求方法。 3. 在控制器类中,我们可以编写一个处理支付请求的方法。在该方法中,我们可以使用微信支付的API来生成预支付订单,包括商品信息、订单号、金额等。 4. 接着,我们可以将预支付订单信息返回给小程序端,小程序可以根据返回的数据调起微信支付的界面。在支付界面中,用户需要输入支付密码或选择其他支付方式进行支付。 5. 支付完成后,微信支付会回调设置的回调URL,我们可以编写一个处理回调的方法来接收微信支付的通知。在该方法中,我们可以对支付结果进行验证,包括订单号、金额、支付状态等。 6. 验证通过后,我们可以更新订单状态为已支付,并进行相关业务处理,比如发货、生成发票等。 7. 最后,我们可以返回支付结果给小程序端,用于展示支付成功或失败的界面。 在以上过程中,我们可以使用Spring MVC提供的注解、组件和工具来简化开发。同时,需要注意支付接口的安全性,比如对支付订单的合法性进行验证,使用HTTPS进行通信等。此外,还需要了解微信支付的相关文档和规范,根据实际需求来定制开发。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值