spring mvc form:radiobutton默认选中_仿spring-framework源码实现手写MVC(一)

84703333b7a4ef140f31b399326a0871.png

在前面一章节这种,我们已经使用代码实现了仿spring-framework源码手写一个IOC容器并且顺利运行起来了,在这一节中,我们尝试先尝试着实现一个简易的MVC,实现请求跳转;

加耀:仿spring-framework源码实现手写一个IOC容器​zhuanlan.zhihu.com
de8b7c96a67302db18acd27fb12e1ada.png

在这里,伴随着后面spring源码的学习中,很多地方都是基于注解@Component实现的,那么在这里,我们先来对上一期的IOC容器再进行优化后,再来开始我们的MVC学习之旅吧;

在上一期的ioc容器中,是不支持组合注解的,这里先来将此处进行粗略的优化一下,使其先在功能上支持组合注解;由于在这一节MVC学习中,我们需要使用到SpringMvc中一个比较经典的注解@RequestMapping,这里先来创建一个自定义的RequestMapping注解;

package core.annotation;

import java.lang.annotation.*;

/**
 * 访问控制层url处理
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestMapping {
    String value() default "/";
}

上述代码中,我们创建了一个名为MyRequestMapping的注解,改注解可以在类上使用,也可以在方法上使用,然后它还有一个默认值value为'/';

然后定义一个访问控制层的标志标识注解@MyController

package core.annotation;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@MyComponent
public @interface MyController {

}

创建好注解后,将上一期的类扫描器ClassPathBeanDefinitionScanner进行一番小改造;由于需要实现组合注解,所以我们需要根据扫描的类,来判断当前类上,是否有我们需要扫描的@MyComponent注解,其代码如下:

/**
 * 判断当前类是否包含组合注解 @MyComponent
 * interface java.lang.annotation.Documented 等 存在循环,导致内存溢出,所以需要排除java的源注解
 * @param classz
 */
private static Class<?> getAnnos(Class<?> classz){
    Annotation[] annotations = classz.getAnnotations();
    for (Annotation annotation : annotations) {
        if (annotation.annotationType() != Deprecated.class &&
                annotation.annotationType() != SuppressWarnings.class &&
                annotation.annotationType() != Override.class &&
                annotation.annotationType() != PostConstruct.class &&
                annotation.annotationType() != PreDestroy.class &&
                annotation.annotationType() != Resource.class &&
                annotation.annotationType() != Resources.class &&
                annotation.annotationType() != Generated.class &&
                annotation.annotationType() != Target.class &&
                annotation.annotationType() != Retention.class &&
                annotation.annotationType() != Documented.class &&
                annotation.annotationType() != Inherited.class &&
                annotation.annotationType() != MyRequestMapping.class
                ) {
            if (annotation.annotationType() == MyComponent.class){
                return classz;
            }else{
               return getAnnos(annotation.annotationType());
            }
        }
    }
    return null;
}

在上述代码中,先排除掉所有的java原生注解,然后排除掉刚创建的注解MyRequestMapping;在这里检验当前类是否存在组合注解,然后将当前类的字节码对象进行返回;也就是在这里可能返回的是@MyController、@MyService等注解,后期实现手写Mybatis框架时,还会有一些MyMapping注解等;编写一个测试方法,运行如下:

324ea31877e81abbdf396193535a2e5c.png

到了这里,我们只需要将上一期中的ClassPathBeanDefinitionScanner的扫描方法进行一点小小的改动就可以了;如下图所示:

3eca1b0187db7f0f76b2f81c312b45ec.png


这样一个简单的组合注解的判别就实现了,虽然在spring源码中的组合注解的实现并不是这样来做的;代码已上传到gitlab;

组合注解实现后,我们再来开始我们的简易版的spring-mvc的实现吧;首先,我们创建一个java web工程;点击idea左上角File--> New Module ,选择java,如下图所示:

28ed10a42663e64aaea78851ef0d9ab2.png

创建完成后,我们在项目的pom文件中,引入tomcat依赖

<dependency>
  <groupId>org.apache.tomcat</groupId>
  <artifactId>tomcat-servlet-api</artifactId>
  <version>9.0.22</version>
</dependency>

同时,由于mvc是需要依赖ioc的,所以我们还需要引入我们在上一期中创建的ioc项目,这里我们采用的是多模块的方式,直接在一个项目中引入另外的模块来实现的;另外,也可以通过多个项目间通过本地或者远程私服来进行jar包之间的依赖的;所以这里引入依赖:

<dependency>
  <groupId>com.jiayao</groupId>
  <artifactId>my-spring-ioc</artifactId>
  <version>1.0-SNAPSHOT</version>
</dependency>

还有就是,由于我们mvc需要获取到访问控制层的方法中的形参信息,在默认情况下通过反射是不能直接拿到参数名的,所以我们还需要使用java8的新特性,这里,我们在pom中指定采用java8插件,这样在反射的时候就可以获取参数的真实名称以及参数类型;

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
    </plugins>
</build>


当这些都已经准备好了后,我们就可以愉快的开始编写代码了;首先,我们先来定义一个DispatcherServlet类,让它继承HttpServlet类并重写它的 doGet、doPost、init方法,我们先让代码中有这些方法,待会再进一步完善;我们在当前类上添加注解@WebServlet(name = "dispatcherServlet"),标记它是一个WebServlet;

接下来,我们打开项目的webapp路径下的WEB-INF路径下的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 xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!--定义一个Servlet-->
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>context.DispatcherServlet</servlet-class>
    </servlet>

    <!--定义Servlet的请求Url规则-->
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <!--此处先拦截所有-->
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
    
</web-app>


在上述xml中,我们先配置一个标签<servlet>,配置好我们刚创建的类,然后为它配置一个请求规则,也就是哪些请求会进入这里;

上面配置的DispatcherServlet类中此时有三个方法,分别为doGet、doPost、init;在这个Servlet初始化时,我们应该先把IOC容器给创建出来,所以有了以下代码:

@WebServlet(name = "dispatcherServlet")
public class DispatcherServlet extends HttpServlet {

    private AnnotationConfigApplicationContext context;
    /**
     * 请求url与访问控制层的映射关系
     */
    private ConcurrentHashMap<String, HandlerMappers> handlerMappersMap = new ConcurrentHashMap<>();

    @Override
    public void init() throws ServletException {
        /**
         * 初始化容器
         */
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.jiayao");
        this.context = context;
        /**
         * 获取所有的Controller类的映射关系
         */
        getTargetController();
    }

在init()方法中,初始化IOC容器,然后获取所有的候选资源中的携带@MyController的类,获取到访问控制层的类之后,我们就需要获取到访问控制层的请求url与类和方法的映射关系,这样在每个方法请求进来后,只需要根据请求的url就可以直接定位到需要请求哪个Controller的哪个方法了;

private void getTargetController() {
    Map<String, BeanDefinition> beanDefinitionMap = context.beanFactory.beanDefinitionMap;
    Collection<BeanDefinition> beanDefinitions = beanDefinitionMap.values();
    beanDefinitions.forEach(beanDefinitio -> {
        if (beanDefinitio.getBeanClass().isAnnotationPresent(MyController.class)) {
            getTarHandlerMappers(beanDefinitio);
        }
    });
}

/**
 * 获取访问控制层的映射关系
 *
 * @param beanDefinitio
 */
private void getTarHandlerMappers(BeanDefinition beanDefinitio) {
    String controllerUrl = "";
    Class<?> beanClass = beanDefinitio.getBeanClass();
    // 获取当前访问控制层全局的url开头
    if (beanClass.isAnnotationPresent(MyRequestMapping.class) && StringUtils.isNotEmpty(beanClass.getAnnotation(MyRequestMapping.class).value())) {
        String value = beanClass.getAnnotation(MyRequestMapping.class).value();
        if (!value.equals("/")) {
            if (value.startsWith("/")) {
                controllerUrl = value;
            } else {
                controllerUrl = "/" + value;
            }
        }
    }
    String methodUrl = "";
    // 获取当前访问控制层所有的方法
    Method[] methods = beanClass.getDeclaredMethods();
    for (Method method : methods) {
        if (method.isAnnotationPresent(MyRequestMapping.class)) {
            String value = method.getAnnotation(MyRequestMapping.class).value();
            if (StringUtils.isNotEmpty(value)) {
                if (!value.startsWith("/")) {
                    methodUrl = controllerUrl + "/" + value;
                } else {
                    methodUrl = controllerUrl + value;
                }
            }
            if (StringUtils.isNotEmpty(methodUrl)) {
                if (handlerMappersMap.containsKey(methodUrl)) {
                    throw new RuntimeException("当前" + beanClass + "的方法的请求路径与" + handlerMappersMap.get(methodUrl).getMethod() + "配置的路径一致导致冲突了");
                }
                // 获取当前方法的参数信息
//                    Parameter[] parameters = method.getParameters();
//                    for (Parameter parameter : parameters) {
//                        String name = parameter.getName();
//                        Type parameterizedType = parameter.getParameterizedType();
//                        Class<?> type = parameter.getType();
//                    }
                handlerMappersMap.put(methodUrl, new HandlerMappers(methodUrl, context.getBean(beanDefinitio.getBeanName()), method));
            } else {
                throw new RuntimeException("请为" + beanClass + "的方法" + method + "配置请求url");
            }
        }
    }
}

在上述代码中,根据候选资源中的类,有携带指定注解的,则进行反射获取其具体信息,获取到@MyRequestMapping注解的请求url,然后封装到一个叫做HandlerMappers的类中,也就是保存请求url与请求对象、请求方法的映射关系;

package handler;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.lang.reflect.Method;

@Data
@AllArgsConstructor
public class HandlerMappers {
    /**
     * 访问的url
     */
    private String url;
    /**
     * 目标对象
     */
    private Object targetObj;
    /**
     * 目标方法
     */
    private Method method;
    /**
     * 存储方法参数
     * .......  TODO  预留到下一次添加参数
     */
}

通过上述方法,我们就可以在项目启动后,将所有的请求url的映射关系都保存起来,以便于请求进来后,可以快速找到需要请求的访问控制层;为了方便,我们这边就直接贴出余下代码:

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    processRequest(request, response);
}

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    processRequest(request, response);
}

private void processRequest(HttpServletRequest request, HttpServletResponse response) {
    // 获取当前请求的url的映射关系
    try {
        String requestURI = request.getRequestURI();
        if (!handlerMappersMap.containsKey(requestURI)) {
            response.getWriter().write("404");
        }
        HandlerMappers handlerMappers = handlerMappersMap.get(requestURI);
        /**
         * 先做没有参数的情况
         */
        Object invoke = handlerMappers.getMethod().invoke(handlerMappers.getTargetObj(), null);
        response.getWriter().write(invoke.toString());
    } catch (IOException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }
}

完成以上步骤后,一个超级简单的MVC就完成了;接下来,为当前项目配置一个tomcat即可运行程序;

de2852826f84ffb95468baa57a9b7c49.png

并且,通过浏览器访问相对应的url,可以执行到相应的访问控制层中;这里先不做方法参数的处理,暂定都是无参的,在下一期中,我们再来进一步完善这个mvc代码吧;

代码地址:https://gitlab.com/qingsongxi/spring-mvc.git

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值