MVC框架一

一、servlet的简化

在使用servlet处理前端发来的请求时,往往需要根据不同的请求创建不同的servlet类。可不可以通过一个servlet类处理所有请求呢?答案是可以的:

package com.fan.servlet;

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

//处理所有路由为f的请求
@WebServlet("/f/*")
public class MyServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}

这个servlet类不进行任何请求的处理与响应,而是将请求按照分类并分发给其它对应的类进行处理。创建一个包com.fan.controller,在这个包中添加各种Controller类处理请求,为了举例,这里加入三个Controller类,分别为OrderController、ProductController和UserController。

OrderController

package com.fan.controller;

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

public class OrderController {
    public void deleteOrder(HttpServletRequest req, HttpServletResponse resp) {
        System.out.println("删除订单!");
    }
}

ProductController

package com.fan.controller;

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

public class ProductController {
    public void onSale(HttpServletRequest req, HttpServletResponse resp) {
        System.out.println("It is onSale!");
    }
    
    public void offSale(HttpServletRequest req, HttpServletResponse resp) {
        System.out.println("It is offSale!");
    }
}

UserController

package com.fan.controller;

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

@FApi("user")
public class UserController {
    public void setVip(HttpServletRequest req, HttpServletResponse resp) {
        System.out.println("设置VIP等级!");
    }

    public void disableUser(HttpServletRequest req, HttpServletResponse resp) {
        System.out.println("冻结用户!");
    }
}

这三个类可以处理订单、商品和用户的操作。比如此时在利用传统请求的方式发送/f/UserController/setVip。这个请求会被MyServlet接收到,此时可以做如下处理:

package com.fan.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
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;

@WebServlet("/f/*")
public class MyServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String prefix = "com.fan.controller";
        //获取地址栏的内容
        String uri = req.getRequestURI();
        try {
            //根据地址栏内容获得应该调用的类和方法
            //这里能够获取到类名UserController
            String apiP = uri.split("/")[3];
            //这里能够获取到方法名setVip
            String apiS = uri.split("/")[4];
            //根据包名加类名反射,实例化对象
             Object obj = Class.forName(prefix + "." + apiP).newInstance();
            //继续反射,获取对应的方法
            Method[] m = obj.getClass().getDeclaredMethod(apiS);
            //调用这个方法,处理请求
            m.invoke(obj);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这样在前端发出请求后,控制栏应该输出

设置VIP等级!

这样通过一个总路由的分发请求的方式确实可行,相应的controller内类的方法参数列表加上HttpServletRequest req, HttpServletResponse resp,就可以和普通的servlet类一样和前端进行交互了。但是,这种方法也有缺点,那就是类名和方法名有时候太长,也会直接暴露出来,可不可以给方法起个简单的“名字”,前端输入通过这些指定处理请求的方法呢?答案是可以的,可以通过注解来完成。

二、通过注解进一步简化servlet

1、注解的简介

在java中可以通过创建注解类自定义注解,在这里我自定义FApi注解

package com.fan.anno;

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

//加入这个注解,可以让我的注解在运行时能够被反射
@Retention(RetentionPolicy.RUNTIME)
public @interface FApi {
    //只定义一个字符串变量value
    String value();
}

这样之前的三个类和各自的方法都可以使用这个注解起名字,以OrderController为例

package com.fan.controller;

import com.fan.anno.FApi;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@FApi("order")
public class OrderController {
    @FApi("del")
    public void deleteOrder(HttpServletRequest req, HttpServletResponse resp) {
        System.out.println("删除订单!");
         try {
            resp.getWriter().print("okok");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2、反射注解找到对应方法

注解已经有了,并且每个注解都是独一无二的,那么该如何进行处理前端发过来的注解呢?例如在地址栏输入/order/del,通过反射所有类的所有注解,首先根据order找到对应类,动态创建类,然后反射遍历这个类里面方法的注解,找到与del对应的方法,通过执行这个方法来处理请求:

package com.fan.servlet;

import com.fan.anno.FApi;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
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;

@WebServlet("/f/*")
public class MyServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String prefix = "com.fan.controller";
        //获取地址栏的内容
        String uri = req.getRequestURI();
        try {
            String apiP = uri.split("/")[3];
            String apiS = uri.split("/")[4];
            //获取部署编译后class文件的路径
            String ctrlPath = req.getServletContext().getRealPath("") + "WEB-INF\\classes\\" + prefix.replace(".", "\\");
            //遍历该路径获取.class文件
            File ctrlDir = new File(ctrlPath);
            File[] fs = ctrlDir.listFiles();
            for (File f : fs) {
                //利用字符串分割获得class的文件名,就是类名。注意不能直接用.分割 而要用\\.分割
                String className = f.getName().split("\\.")[0];
                //利用类名进行反射,得到类上的注解
                Object obj = Class.forName(prefix + "." + className).newInstance();
                //获取指定注解及其属性值
                FApi fApiP = obj.getClass().getAnnotation(FApi.class);
                //匹配一级路径
                if (fApiP.value().equals(apiP)) {
                    //获取所有方法并根据注解进行匹配
                    Method[] ms = obj.getClass().getDeclaredMethods();
                    for (Method m : ms) {
                        FApi fApiS = m.getAnnotation(FApi.class);
                        if (fApiS.value().equals(apiS)) {
                            //匹配到方法就可以执行
                            m.invoke(obj, req, resp);
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

因为java文件在编译后文件名不变,所以通过编译路径的.class文件来获得所有类的类名。这样最后在控制台可以输出

删除订单!

在网页上会显示

okok

三、引入mvc

一个总路由通过注解分发请求的方式,其实已经有前人做了总结,并且写成了框架方便我们使用,这里介绍mvc。本次介绍mvc是使用最简单的配置先将mvc运行起来。首先导入需要的jar包,在web项目的WEB-INF文件夹里创建web.xml文件,在文件内写入如下配置:



    
        spring
        org.springframework.web.servlet.DispatcherServlet
    

    
        spring
        /
    


因为url-pattern设置为了/所以能够处理整个网站的所有请求。除了这个配置文件,还要在同目录下创建一个名为spring-servlet.xml的配置文件。注意这里的文件名一定要和我写的一样,因为mvc框架在运行的时候,会默认在WEB-INF文件夹内读取spring-servlet.xml文件(当然这个配置文件的名字和路径也可以自定义),本着配置最简单的原则,先这样创建,并且在配置文件中写入:




    
    

这个配置是最简单的配置,有了这个就可以做一些基本的实验了,完整的配置远不止这些。所谓包扫描与之前在编译路径中遍历注解一样。做完简单的配置后,如何处理请求呢?此时已经不需要我们自己写总路由了,只需要写好具体的类和方法就可以了:

package com.fan.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/vip")
    public void setVip() {
        System.out.println("设置会员!");
    }

    @RequestMapping("/login")
    public ModelAndView login() {
        System.out.println("okok");
    }
}

解释一下这里注解@RequestMapping("/user"),这个注解就相当于我之前自定义的FApi,里面放入对应“别名”。@Controller表示这个类需要实例化才可以使用里面的方法,所以框架在处理对应请求时会动态创建类,不写这个注解,就会用类名直接调用方法。此时在地址栏输入/user/login,控制栏就会显示如下语句

okok

那么后端如何接受前端传过来的数据呢?比如我在地址栏这样输入/user/login?account=fan&password=123,后端想要接到这些参数有三种方法:

1、参数列表设置对应参数

这里需要注意,参数名一定要和地址栏内传参使用的名称相同

@RequestMapping("/login")
    public ModelAndView login(String account,String password) {
        System.out.println(String account + "---" + String password);
    }

2、创建实体类接受数据

创建一个com.fan.entity,专门存放实体类。在这里,我创建了一个User类

package com.fan.entity;

public class User {
    private int id;
    private String account;
    private String password;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

这样在方法中可以将实体类作为参数

@RequestMapping("/login")
    public void login(User user) {
        String account = user.getAccount();
        String password = user.getPassword();
        System.out.println(account + "---" + password);
    }

这样做的好处是:当传入的参数过多时,可以用实体类一一获取,参数列表也可以精简

3、通过HttpServletRequest 获取

类似于我们自己反射的时候,这里也可以将前端的请求和回应作为参数传入方法中,所以类似servlet类,可以接到前端传入的值

@RequestMapping("/login")
      public void login(HttpServletRequest req, HttpServletResponse resp) {
        String account = req.getParameter("account");
        String password = req.getParameter("password");
        System.out.println(account + "---" + password);
    }

这三种方法都可以获得前端传入的值,所以它们的都会在控制台打印输出

fan---123

后端向前端传输数据,除了可以用(HttpServletRequest req, HttpServletResponse resp,还可以用mvc自带的ModelAndView。下面将利用这个类来处理传统请求,抓取视图返回数据。

1、完善spring-servlet.xml的配置




    
    
    
    
        
        
    


在这里property name="prefix" value="/page"/ 表示文件路径在page文件夹下,property name="suffix" value=".jsp"表示文件类型为jsp。

2、ModelAndView的使用

@RequestMapping("/login")
    public ModelAndView login(User user) {
        //构造参数是指抓取的视图的文件名
        ModelAndView mav = new ModelAndView("/resA");
        System.out.println("okok");
        //这个方法相当于在请求域内传入参数
        mav.addObject("resA", "登录成功!!!");
        return mav;
    }

3、在page文件夹下创建视图

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<% String path = request.getContextPath();
    String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
            + path + "/";%>

    
        
        标题
        
        
    
    
        
        

${resA}

经过这三步后,在地址栏输入/user/login,后端就会抓取视图,浏览器会跳转页面,并且显示:登录成功!!!

4、对配置文件中bean的详解

注意在后端new ModelAndView("/resA");时,并没有写全视图文件的路径,这是因为有以下配置



    
        
        
    

由此我们可以推测,这个InternalResourceViewResolver类里应该有如下代码

public class InternalResourceViewResolver {
    private String prefix;
    private String suffix;

    public String getPrefix() {
        return prefix;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    public String getSuffix() {
        return suffix;
    }

    public void setSuffix(String suffix) {
        this.suffix = suffix;
    }
}

四、spring-servlet.xml文件的自定义

一般会将这个配置文件移动到src下一个config文件夹中,如果这样,web.xml文件的配置就需要更改




    
        spring
        org.springframework.web.servlet.DispatcherServlet
        
        
            
            contextConfigLocation
            
            
                classpath:/config/spring-servlet.xml
            
        
    

    
        spring
        /
    
    
    
    
        contextConfigLocation
        classpath:/config/spring-servlet.xml
    


五、总结

所谓MVC就是指:数据、视图和控制,代表着整个前后端交互的数据传输、视图转换和逻辑控制。其中C是核心,它负责接受传
递处理数据M,控制视图C的的渲染。这是一种界面层交互设计方式,也是一种思想,并不是只有在java中存在,也可以扩展到
其它语言其他领域。

你可能感兴趣的:(MVC框架一)