第三十八章 Spring之假如让你来写MVC——适配器篇

Spring源码阅读目录

第一部分------IOC篇

第一章 Spring之最熟悉的陌生人------IOC
第二章 Spring之假如让你来写IOC容器------加载资源篇
第三章 Spring之假如让你来写IOC容器------解析配置文件篇
第四章 Spring之假如让你来写IOC容器------XML配置文件篇
第五章 Spring之假如让你来写IOC容器------BeanFactory和FactoryBean
第六章 Spring之假如让你来写IOC容器------Scope和属性填充
第七章 Spring之假如让你来写IOC容器------属性填充特别篇:SpEL表达式
第八章 Spring之假如让你来写IOC容器------拓展篇
第九章 Spring之源码阅读------环境搭建篇
第十章 Spring之源码阅读------IOC篇

第二部分------AOP篇

第十一章 Spring之不太熟的熟人------AOP
第十二章 Spring之不得不了解的内容------概念篇
第十三章 Spring之假如让你来写AOP------AOP联盟篇
第十四章 Spring之假如让你来写AOP------雏形篇
第十五章 Spring之假如让你来写AOP------Joinpoint(连接点)篇
第十六章 Spring之假如让你来写AOP------Pointcut(切点)篇
第十七章 Spring之假如让你来写AOP------Advice(通知)上篇
第十八章 Spring之假如让你来写AOP------Advice(通知)下篇
第十九章 Spring之假如让你来写AOP------番外篇:Spring早期设计
第二十章 Spring之假如让你来写AOP------Aspect(切面)篇
第二十一章 Spring之假如让你来写AOP------Weaver(织入器)篇
第二十二章 Spring之假如让你来写AOP------Target Object(目标对象)篇
第二十三章 Spring之假如让你来写AOP------融入IOC容器篇
第二十四章 Spring之源码阅读------AOP篇

第三部分------事务篇

第二十五章 Spring之曾经的老朋友------事务
第二十六章 Spring之假如让你来写事务------初稿篇
第二十七章 Spring之假如让你来写事务------铁三角篇
第二十八章 Spring之假如让你来写事务------属性篇
第二十九章 Spring之假如让你来写事务------状态篇
第三十章 Spring之假如让你来写事务------管理篇
第三十一章 Spring之假如让你来写事务------融入IOC容器篇
第三十二章 Spring之源码阅读------事务篇

第四部分------MVC篇

第三十三章 Spring之梦开始的地方------MVC
第三十四章 Spring之假如让你来写MVC------草图篇
第三十五章 Spring之假如让你来写MVC------映射器篇
第三十六章 Spring之假如让你来写MVC------拦截器篇
第三十七章 Spring之假如让你来写MVC------控制器篇
第三十八章 Spring之假如让你来写MVC------适配器篇
第三十九章 Spring之假如让你来写MVC------番外篇:类型转换
第四十章 Spring之假如让你来写MVC------ModelAndView篇
第四十一章 Spring之假如让你来写MVC------番外篇:数据绑定
第四十二章 Spring之假如让你来写MVC------视图篇
第四十三章 Spring之假如让你来写MVC------上传文件篇
第四十四章 Spring之假如让你来写MVC------异常处理器篇
第四十五章 Spring之假如让你来写MVC------国际化篇
第四十六章 Spring之假如让你来写MVC------主题解析器篇
第四十七章 Spring之假如让你来写MVC------闪存管理器篇
第四十八章 Spring之假如让你来写MVC------请求映射视图篇
第四十九章 Spring之假如让你来写MVC------番外篇:属性操作
第五十章 Spring之假如让你来写MVC------融入IOC容器篇
第五十一章 Spring之源码阅读------MVC篇


文章目录


前言

对于Spring一直都是既熟悉又陌生,说对它熟悉吧,平时用用没啥问题,但面试的时候被问的一脸懵逼,就很尴尬,都不好意思在简历上写着熟悉Spring了

所以决定花点时间研究研究Spring的源码。主要参考的书籍是:《Spring源码深度解析(第2版)》、《Spring揭秘》、《Spring技术内幕:深入解析Spring架构与设计原理(第2版)》


书接上回,在上篇 第三十七章 Spring之假如让你来写MVC------控制器篇 中,A君 已经完成了 控制器 部分的功能了。接下来看看 A君 会有什么骚操作吧

尝试动手写IOC容器

出场人物:A君 (苦逼的开发)、老大(项目经理)

背景:老大 要求 A君 在一周内开发个简单的 IOC容器

前情提要:A君 已经完成了 控制器 部分的功能了 。。。

第三十四版 适配器

"A君 呐,你用if..else来判断调用哪个处理器就很灵性了。" 老大 感叹道

"额。" A君 不知道怎么结果话茬,只能装傻充愣了

"这种拓展性太低了,后边还有其他处理器的实现方式呢。这样子往后稍微发展下,你的代码就变成了传说中的 屎山代码 。还有一点,就是关于请求参数的处理,要知道一个请求可以携带很多参数的,而你现在这个实现,用户只能去request里获取,这一点用户体验非常的糟糕。去把这两部部分内容处理下吧。" 说完,老大 就没再说什么了

看到 老大 没有继续往下说的意思,看来是要自己发挥。A君 默默的退了出去,准备着手干活了

对于这种类型适配器,A君 其实并不算陌生,早在之前实现 IOC容器 的时候,A君 就已经接触过,只不过当时是根据属性类型找到对应的转换器进行类型转换。而这次,只是改成根据不同的 控制器 找到对应的 适配器 而已,其本质本无实际上的区别。那么 适配器 的接口就好定义了,必然存在着两个方法,一个是能不能转换,另一个是进行转换。基本盘有了,接下来就要考虑特殊性了,由于HTTP中定义了Last-Modified。其规范如下:

这个属性跟缓存息息相关,如果没有超过对应时间,则服务器可以直接不处理,返回上次结果。那么整体的接口定义就出来了,A君 新增HandlerAdapter接口,代码如下:

java 复制代码
/**
 * 控制器的适配器,找到对应的控制器
 */
public interface HandlerAdapter {
    /**
     * 是否可以转换
     *
     * @param handler
     * @return
     */
    boolean supports(Object handler);

    /**
     * 处理请求
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    Object handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

    /**
     * 如果没有默认实现,默认返回-1
     * 这个方法放在这里感觉并不合适,LastModified和适配器并有什么关系,违反了接口单一原则,并不符合Spring一贯的作风
     * 新版本已经弃用,这里考虑到老项目
     *
     * @param request
     * @param handler
     * @return
     */
    @Deprecated
    long getLastModified(HttpServletRequest request, Object handler);
}

Last-Modified的处理方法放在这里感觉并不合适,Last-Modified和适配器本身并没有什么关系,违反了接口单一原则。按理说,适配器只负责找到处理对应的控制器进行处理,并不会去关心Last-Modified这些东西,此处的设计不符合Spring一贯的作风,不知道当初是基于什么考虑才如此设计,这里为了贴近Spring,所以也加上getLastModified方法。在Spring5.3.9后续版本中,该方法已经标记成弃用

好了,接口出来后,先挑个简单的练练手,A君 盯上了ControllerServlet,原因也简单,实现了对应接口的,其实是最好处理的,只需要强转后调用该接口方法就行了。这里 A君 就以Controller适配器为例,新增SimpleControllerHandlerAdapter类,代码如下:

java 复制代码
/**
 * Controller相关接口适配
 */
public class SimpleControllerHandlerAdapter implements HandlerAdapter {

    @Override
    public boolean supports(Object handler) {
        return (handler instanceof Controller);
    }

    @Override
    public Object handle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

        return ((Controller) handler).handleRequest(request, response);
    }

    @Override
    @SuppressWarnings("deprecation")
    public long getLastModified(HttpServletRequest request, Object handler) {
        if (handler instanceof LastModified) {
            return ((LastModified) handler).getLastModified(request);
        }
        return -1L;
    }
}

简单过后,A君 就开始头疼了,简单之所以简单,是因为实现了对应接口,方法参数、返回值都是固定的。而基于注解的 控制器 ,这两个没有一个是能确定的,不能确定就意味着:需要框架去推算匹配,那么参数处理就会变得异常的麻烦。没办法,只能先易后难。A君 谨遵 老大 的教诲,对于有公共内容的,提取出来一个抽象类。于是,A君 新增AbstractHandlerMethodAdapter类,代码如下:

java 复制代码
public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered {

    @Override
    public final boolean supports(Object handler) {
        return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
    }

    @Override
    public final Object handle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

        return handleInternal(request, response, (HandlerMethod) handler);
    }

    protected abstract Object handleInternal(HttpServletRequest request,
                                             HttpServletResponse response, HandlerMethod handlerMethod) throws Exception;

    protected abstract boolean supportsInternal(HandlerMethod handlerMethod);

    @Override
    @SuppressWarnings("deprecation")
    public final long getLastModified(HttpServletRequest request, Object handler) {
        return getLastModifiedInternal(request, (HandlerMethod) handler);
    }

    @Deprecated
    protected abstract long getLastModifiedInternal(HttpServletRequest request, HandlerMethod handlerMethod);

}

简单的完成了,接下来就得考虑困难的事了,当务之急,就是要解决参数匹配的问题,不然 注解适配类 无从实现

request参数

要想自动适配 控制器 参数,A君 得先知道哪些可以成为入参,也就是一个request请求中,哪些可以作为 控制器 的参数。A君 在翻阅 Servlet规范 时,看到这些东西,如下:

  1. 请求参数:
  2. 属性:

3.头:

那么正常情况下,request中:属性(Attributes)、请求头(Headers)、请求参数(Params) 都可以作为 控制器 的入参。明白参数从哪里之后,就可以先把这部分东西抽象化了。这里要注意的是,虽然 A君 一直以 Servlet规范 作为例子,实际上在Web环境下,不仅仅 Servlet 一种规范,还有很多不同的规范,像:WebSocket、 WebFlux、Portlet 等,所以这里定义接口,更是为了通用性,适配这些规范。A君 定义 RequestAttributes 接口,用来封装 属性(Attributes) 的相关操作。代码如下:

java 复制代码
/**
 * request请求属性
 */
public interface RequestAttributes {

    int SCOPE_REQUEST = 0;

    int SCOPE_SESSION = 1;


    /**
     * request作用域
     */
    String REFERENCE_REQUEST = "request";
    /**
     * session作用域
     */
    String REFERENCE_SESSION = "session";

    Object getAttribute(String name, int scope);

    void setAttribute(String name, Object value, int scope);

    void removeAttribute(String name, int scope);

    String[] getAttributeNames(int scope);

    /**
     * 注册回调
     *
     * @param name
     * @param callback
     * @param scope
     */
    void registerDestructionCallback(String name, Runnable callback, int scope);

    /**
     * 处理引用类型
     *
     * @param key
     * @return
     */
    Object resolveReference(String key);

    /**
     * 获取SessionId
     *
     * @return
     */
    String getSessionId();

    /**
     * 获取Session同步锁
     *
     * @return
     */
    Object getSessionMutex();

}

这个接口只是规定了 属性(Attributes) 的相关操作,显然是远远不够的,还需要 请求头(Headers)、请求参数(Params) 的相关操作,于是,A君 对其进行拓展,定义WebRequest接口。代码如下:

java 复制代码
public interface WebRequest extends RequestAttributes {

    /**
     * 获取请求头
     *
     * @param headerName
     * @return
     */
    String getHeader(String headerName);

    String[] getHeaderValues(String headerName);

    Iterator<String> getHeaderNames();

    String getParameter(String paramName);

    String[] getParameterValues(String paramName);

    Iterator<String> getParameterNames();

    Map<String, String[]> getParameterMap();

    Locale getLocale();

    String getContextPath();

    String getRemoteUser();

    Principal getUserPrincipal();

    boolean isUserInRole(String role);

    boolean isSecure();

    /**
     * 检查资源是否过期
     *
     * @param lastModifiedTimestamp
     * @return
     */
    boolean checkNotModified(long lastModifiedTimestamp);

    boolean checkNotModified(String etag);

    boolean checkNotModified(String etag, long lastModifiedTimestamp);

    String getDescription(boolean includeClientInfo);

}

前文提到,由于存在着多种规范,那么对于获取其真实的请求或者响应对象就很有必要了,A君 不可以预判到用户要用哪种协议,所以只能再定义个接口,用来获取真实的请求对象、响应对象。新增NativeWebRequest接口,代码如下:

java 复制代码
public interface NativeWebRequest extends WebRequest {
    /**
     * 获取真实请求对象
     *
     * @return
     */
    Object getNativeRequest();

    /**
     * 获取真实响应对象
     *
     * @return
     */
    Object getNativeResponse();

    <T> T getNativeRequest(Class<T> requiredType);

    <T> T getNativeResponse(Class<T> requiredType);
}

接口定义完成之后,接下来就是实现了,虽然存在着多种规范,但 A君 并不太关心,起码现在不用关心,这里是 MVC 的主场。A君 只关注 Servlet 的实现,那问题就简单了,这些东西只需要从 Servlet容器 中获取就行了。A君 新增ServletWebRequest类,代码如下:

java 复制代码
public class ServletWebRequest extends ServletRequestAttributes implements NativeWebRequest {
	
	@Override
    public String getParameter(String paramName) {
        return getRequest().getParameter(paramName);
    }
    @Override
    public String getHeader(String headerName) {
        return getRequest().getHeader(headerName);
    }
    //省略其他方法
}
参数类型转换

解决了参数的来源问题,现在就可以开始进行参数的转换了。要知道request请求过来的参数可只有字符串类型或字节流,字节流现在不在考虑的范围之内,那还需要把字符串转成 控制器 对应的类型才行。所谓自动适配,无非就是把请求参数转成方法参数罢了。转换接口依旧是那两板斧,能处理吗?进行处理!玩不出什么花来。于是,A君 照猫画虎,定义HandlerMethodArgumentResolver接口,代码如下:

java 复制代码
/**
 * 参数转换接口
 */
public interface HandlerMethodArgumentResolver {

    boolean supportsParameter(MethodParameter parameter);

    Object resolveArgument(MethodParameter parameter, NativeWebRequest webRequest) throws Exception;
}

接着就是实现了,类那么多,A君 还没颠到全部实现的地步,只要实现主流的类就行了,剩余的部分嘛?就看用户自己发挥了。A君 决定先从两个最重要的参数开始,那就是:requestresponse。两个都是类似的,这里就以request为例,A君 新增ServletRequestMethodArgumentResolver类,那么问题来了,要支持那些类型呢? 对于Web来说,request可谓是举足轻重,主要是从request可以拿到太多的东西了,比如:ServletRequestHttpSessionPrincipalInputStream等。那么能从request中获取的东西,就是ServletRequestMethodArgumentResolver支持的类型。代码如下:

java 复制代码
public class ServletRequestMethodArgumentResolver implements HandlerMethodArgumentResolver {
	@Override
    public boolean supportsParameter(MethodParameter parameter) {
        Class<?> paramType = parameter.getParameterType();
        return (WebRequest.class.isAssignableFrom(paramType) ||
                ServletRequest.class.isAssignableFrom(paramType) ||
                HttpSession.class.isAssignableFrom(paramType) ||
                (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
                Principal.class.isAssignableFrom(paramType) ||
                InputStream.class.isAssignableFrom(paramType) ||
                Reader.class.isAssignableFrom(paramType) ||
                HttpMethod.class == paramType);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter,
                                  NativeWebRequest webRequest) throws Exception {
        Class<?> paramType = parameter.getParameterType();
        if (WebRequest.class.isAssignableFrom(paramType)) {
            if (!paramType.isInstance(webRequest)) {
                throw new IllegalStateException("Current request is not of type [" + paramType.getName() + "]: " + webRequest);
            }
            return webRequest;
        }
        if (ServletRequest.class.isAssignableFrom(paramType)) {
            return resolveNativeRequest(webRequest, paramType);
        }
        return resolveArgument(paramType, resolveNativeRequest(webRequest, HttpServletRequest.class));
    }
}

这还是很简单的,一堆if...esle就行了,A君 感叹道。接着还需要支持下基本类型,首先要考虑的是如何从request中获取到参数,HTTP请求只有字符串或字节流,按照类型匹配显然是不合适的。现在 参数转换器 要做的事情,就是根据名称从request对应的值,转成 控制器 参数的类型:
解析 是 否 request参数 参数类型 是否匹配类型 成功转换 转换失败 控制器参数 结束

A君 先定义一个NamedValueInfo类,用以包装参数名相关信息。代码如下:

java 复制代码
protected static class NamedValueInfo {

        private final String name;

        private final boolean required;


        private final String defaultValue;

        public NamedValueInfo(String name, boolean required, String defaultValue) {
            this.name = name;
            this.required = required;
            this.defaultValue = defaultValue;
        }
    }

接着,A君 思考了下:根据方法参数名(方法参数名的获取在AOP 相关章节中有过说明,这里就不在进行赘述了 ),去request中获取值,不论是 属性(Attributes)、请求头(Headers)、请求参数(Params) 都是从 request 中获取的,只是地方不一样罢了,那这就可以提取一个抽象类了。于是,A君 定义AbstractNamedValueMethodArgumentResolver类,代码如下:

java 复制代码
public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver {
	@Override
    public final Object resolveArgument(MethodParameter parameter, NativeWebRequest webRequest) throws Exception {
    	/**
         * 获取目标参数名
         */
        NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
        MethodParameter nestedParameter = parameter.nestedIfOptional();

        Object resolvedName = namedValueInfo.name;
        if (resolvedName == null) {
            throw new IllegalArgumentException(
                    "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
        }
		/**
         * 从request获取值,由子类实现
         */
        Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
        if (arg == null) {
            if (namedValueInfo.defaultValue != null) {
                arg = namedValueInfo.defaultValue;
            } else if (namedValueInfo.required && !nestedParameter.isOptional()) {
                handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
            }
            arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
        } else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
            arg = namedValueInfo.defaultValue;
        }

        handleResolvedValue(arg, namedValueInfo.name, parameter, webRequest);

        return arg;
    }
    //省略其他方法
}

好了,抽象类已经抽取完毕,那么 属性(Attributes)、请求头(Headers)、请求参数(Params) 都大同小异了,只需要去各自的区域取值就行了。A君 这里以 请求参数(Params) 为例,新增RequestParamMethodArgumentResolver类,代码如下:

java 复制代码
public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver
        implements UriComponentsContributor {

	@Override
    public boolean supportsParameter(MethodParameter parameter) {
        if (parameter.hasParameterAnnotation(RequestParam.class)) {
            /**
             * map类型
             */
            if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
                RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
                return (requestParam != null && StringUtils.hasText(requestParam.name()));
            } else {
                return true;
            }
        } else {
            /**
             * 是否是Optional
             */
            parameter = parameter.nestedIfOptional();
            if (this.useDefaultResolution) {
                return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
            } else {
                return false;
            }
        }
    }
	@Override
    protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
        HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
        Object arg = null;
        if (arg == null) {
            String[] paramValues = servletRequest.getParameterValues(name);
            if (paramValues != null) {
                arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
            }
        }
        return arg;
    }
    //省略其他方法
}

好了,现在都准备好了,剩下的就是把这些参数处理器进行一下整合,方便别人使用。A君 在定义HandlerMethodArgumentResolverComposite类,这个不负责具体实现,而是调用别的类来实现转换,类似于一个管理者的角色。代码如下:

java 复制代码
public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {

    private final List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>();

    private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache =
            new ConcurrentHashMap<>(256);


	 @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return getArgumentResolver(parameter) != null;
    }

    private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
        HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
        if (result == null) {
            for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
                if (resolver.supportsParameter(parameter)) {
                    result = resolver;
                    this.argumentResolverCache.put(parameter, result);
                    break;
                }
            }
        }
        return result;
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, NativeWebRequest webRequest) throws Exception {

        HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
        if (resolver == null) {
            throw new IllegalArgumentException("Unsupported parameter type [" +
                    parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
        }
        return resolver.resolveArgument(parameter, webRequest);
    }

好嘞,现在处理参数部分也完事了,饶了一大圈,总算把参数处理完了,现在可以继续前行了

注解适配器

解决完最麻烦的参数处理之后,剩下的 注解适配器 就没有什么东西了,跟反射调用基本一样了。A君 新增RequestMappingHandlerAdapter类,代码如下:

java 复制代码
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
        implements BeanFactoryAware, InitializingBean {
	
	private HandlerMethodArgumentResolverComposite argumentResolvers;
	protected Object invokeHandlerMethod(HttpServletRequest request,
                                         HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        try {
            ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
            if (this.argumentResolvers != null) {
                invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
            }
            if (this.returnValueHandlers != null) {
                invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
            }
            invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);


            return invocableMethod.invokeAndHandle(webRequest);
        } finally {
            webRequest.requestCompleted();
        }
    }
    //省略其他代码
}
改造DispatcherServlet

适配器 弄完之后,A君 想起还需要对DispatcherServlet进行改造,毕竟当初折腾 适配器 的目的,不就是为了替换DispatcherServlet中的if...else吗?改造如下:

  1. 新增 适配器 类:
  1. 替换doDispatch方法中的if...else
测试

现在一切都准备好了,可以进入检验成果的时候了。A君 修改HelloController,新增一个参数。代码如下:

java 复制代码
@Controller
public class HelloController {

    @RequestMapping("/hello")
    public String sayHello(HttpServletRequest req, HttpServletResponse resp, String str) {
        String key = "message";
        String message = (String) req.getAttribute(key);
        if (message == null) {
            message = "";
        }
        req.setAttribute(key, message + " V34 HandleMapping! " + str);
        return "hello";
    }
}

其他测试代码不需要变动,编写测试代码如下:

java 复制代码
 @Test
    public void v34() throws Throwable {
        System.out.println("############# 第三十四版: 适配器篇 #############");
        Tomcat tomcat = new Tomcat();
        //设置端口
        tomcat.setPort(8082);
        //设置静态资源路径
        String webApp = new File("src/main/resources/v34").getAbsolutePath();
        Context context = tomcat.addWebapp("/test/", webApp);
        tomcat.start();
        //挂起
        tomcat.getServer().await();
    }

测试结果如下:

后台成功接收到参数并显示出来了,说明 A君 的努力并没有白费。OK!这下子总算弄好了,也可以向 老大 交差了


总结

正所谓树欲静而风不止,欲知后事如何,请看下回分解(✪ω✪)

相关推荐
sin22013 小时前
springMVC---resultful风格
mvc
sin22014 小时前
springMVC---常用注解
mvc
会code的厨子5 小时前
Spring底层核心原理解析
java·spring
一小只因程序猿5 小时前
《异步编程之美》— 全栈修仙《Java 8 CompletableFuture 对比 ES6 Promise 以及Spring @Async》
前端·javascript·jvm·spring·es6
造梦师阿鹏5 小时前
【SpringBoot】用一个常见错误说一下@RequestParam属性
java·spring boot·后端·spring
码蜂窝编程官方8 小时前
【含开题报告+文档+PPT+源码】基于SpringBoot+Vue的医院挂号预约管理系统
java·vue.js·spring boot·后端·spring
Mr_sun.10 小时前
Day05-后端Web基础——Tomcat&Servlet&HTTP协议&SpringBootWeb入门
前端·servlet·tomcat
华如锦11 小时前
【JAVA架构】开发学校内部论文,文章查重系统【python】
java·开发语言·人工智能·spring boot·python·spring
zzyh12345614 小时前
springcloudgateway原理
spring