精华内容
下载资源
问答
  • SpringMVC: 前端控制器
    千次阅读
    2019-05-21 14:05:48

    在SpringMVC中, 开发者不在需要关心Servlet等组件的机制. 只需要按照SpringMVC的约定(框架使用方法): 在控制器中添加方法并声明可以处理的请求, 将数据保存至Model中返回视图即可.

    SpringMVC在J2EE上进行了封装, 让开发者的工作更专注于业务. 在J2EE中, 只有Filter和Servlet才可以处理请求, 由于Filter更偏向于进行验证处理(如请求的合法性等), 因此处理业务请求需要由Servlet完成. 在SpringMVC中开发者不需要实现Servlet就可以处理请求. 下面来分析一下SpringMVC的实现机制.

    1. 什么是前端控制器

    使用J2EE开发时, 每次请求都需要一个Servlet处理, 整个过程可以模拟为患者在医院就医的过程. 每个患者去医院相当于一次请求, 那么处理这次请求的Servlet就是一名医生.

    去小型诊所就医时可以直接去医生办公室办理手续后就可以接受诊治. 每个医生都了解如何给患者办理手续. 但对于大型医院来说, 由于患者和科室较多, 让每个医生学习这些非专业的流程是比较浪费资源的. 大型医院会设置前台, 由前台负责给患者办理手续, 办理完成后根据患者的病情交由不同学科的医生进行诊治. 当有医生休息, 调岗, 离职或新加入时都需要按照统一的格式填写相应的变动信息. 前台定期收集整理, 保证能够准确的了解当日的医生出诊信息. 同时, 将相同学科的医生安排在同一办公室, 将出诊医生安排在相对集中的办公区, 这样既方便前台搜集医生出诊信息, 也便于对医生的管理.

    所有患者来医院就医时都由前台统一接待并办理手续, 根据收集的医生出诊信息引领至对应的医生就行诊治. 通过前台实现了对患者就医流程的统一管理.

    SpringMVC就是按照上述思路进行处理的, 他也有这样一个前台, 叫做前端控制器.

    2. 前端控制器的处理流程

    上述文案转换为SpringMVC描述:

    每个控制器方法(医生)中声明可以处理的请求(填写可以诊治的病). 前端控制器(前台)在每日上班(每次项目启动)时, 去医生办公区(控制器所在目录)的每个科室(控制器)收集(加载)各个医生(控制器方法)可以诊治的病(处理的请求), 汇总并整理成文档(方法与URL映射Mapping). 当患者(客户端)来就医(发送请求)时, 由前台接待(前端控制器处理所有请求), 前台根据患者的病情(访问的请求URL)从整理的文档(Mapping)中找到可以诊治该病的医生(控制器方法), 并交由(分发)相应的医生进行诊治(执行业务逻辑).

    由此可见, 前端控制器是一个负责处理所有请求的Servlet.

    3. 前端控制器的实现

    上述已经说明了SpringMVC前端控制器的实现原理, 下面通过代码实现一个前端控制器.

    3.1. 配置控制器目录(指定医生办公区):

    大规模的系统中可能有成百上千个类, 如果前端控制器在加载控制器时扫描所有的类势必会严重影响加载速度, 因此我们应当告知前端控制器需要被扫描的控制器所在的具体目录, 也就是说需要前端控制器配置参数. 这样前端控制器在扫描时只需要遍历指定目录下的类即可.

    常见的方式是在定义前端控制器(web.xml配置Servlet)时配置初始化参数. 但随着框架功能不断的增加, 前端控制器的配置项会越来越多, 这种方式并不灵活. 因此需要采用一种更加灵活的方式: 通过XML配置, 在前端控制器加载时通过读取XML配置文件并解析获取控制器所在目录.

    配置控制器目录的XML格式如下: controller节点为控制器相关配置, package属性为控制器所在目录.

    <mvc>
        <controller package="com.atd681.xc.ssm.controller" />
    </mvc>
    复制代码

    3.2. URL映射注解(填写可以诊治的病的表格)

    定义控制器方法设置可以处理请求的注解.

    @Documented
    @Target(ElementType.METHOD)
    @Inherited
    @Retention(RetentionPolicy.RUNTIME)
    public @interface RequestMapping {
    
        // 请求URL
        String url();
    
        // HTTP方法
        String method() default "GET";
    
    }
    复制代码

    控制器方法统一使用注解声明可以处理的请求.

    public class UserController {
    
        // 处理"/list"请求
        @RequestMapping(url = "/list")
        public String userList() {
            return "";
        }
    
        // 处理"/detail"请求
        @RequestMapping(url = "/detail")
        public String userDetail() {
            return "";
        }
    
    }
    复制代码

    3.3. 实现前端控制器(医院前台)

    3.3.1. 项目启动时(前台每日上班)加载配置文件

    J2EE中规定, Servlet在被加载时会执行init方法, 因此我们可以把加载控制器的过程写在init方法中.

    根据约定优于配置的原则: 我们约定好配置文件的路径在classpath下, 名称为mvc.xml, 有些场景下用户需要自定义配置文件的路径和名称, 因此我们也需要支持用户自定义, 自定义配置文件路径和名称时在web.xml中通过名为configLocation的参数传入前端控制器.

    /**
     * 前端控制器(负责处理所有请求)
     */
    public class DispatcherServlet extends HttpServlet {
    
        // 默认MVC配置文件路径
        private static final String DEFAULT_CONFIG_LOCATION = "mvc.xml";
    
        /**
         * 初始化Servlet. 容器初始化Servlet时调用, 加载配置文件初始化MVC相关组件(控制器,视图解析器等)
         */
        @Override
        public void init() throws ServletException {
    
            // 获取用户自定义的配置文件路径
            String configLocation = getInitParameter("configLocation");
    
            // 未定义配置文件路径, 使用默认配置文件路径
            if (configLocation == null || "".equals(configLocation)) {
                configLocation = DEFAULT_CONFIG_LOCATION;
            }
    
            try {
    
                // 开始加载配置文件(JDom解析XML)
                String classpath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
                Document doc = new SAXBuilder().build(new File(classpath, configLocation));
    
                // 解析配置文件中控制器的配置
                initController(doc);
    
            } catch (Exception e) {
                throw new ServletException("加载配置文件错误", e);
            }
    
        }
    	
    }
    
    复制代码

    3.3.2. 加载控制器(去办公区各科室搜集医生可以诊治的病的表格)

    在前端控制器中定义全局变量, 用来保存控制器方法与URL的映射关系, 以下简称urlMapping.

    // URL映射MAP(K:URL, V:对应的控制器方法)
    private static Map<String, Method> urlMappings = new HashMap<String, Method>();;
    复制代码

    在配置文件中获取控制器目录并遍历该目录, 获取每个控制器的文件名称. 通过JAVA反射分别加载控制器, 将每个方法及对应的URL保存至映射urlMapping中.

    /**
     * 解析配置文件中的控制器配置
     * 
     * @param doc XML配置文件
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    private void initController(Document doc) throws Exception {
    
        // 配置格式:<controller package="com.atd681.xc.ssm.controller"/>
        // package为控制器所在目录, 模拟SpringMVC配置文件中的控制器包扫描
        List<Element> controllerEle = doc.getRootElement().getChildren("controller");
        if (controllerEle == null) {
            throw new Exception("请配置Controller节点.");
        }
    
        // 获取配置文件中的控制器所在目录
        String controllerPackage = controllerEle.get(0).getAttributeValue("package");
        if (controllerPackage == null) {
            throw new Exception("Controller的package属性必须设置.");
        }
    
        // 获取控制器目录的在磁盘中的绝对路径(D:\atd681-xc-ssm\com\atd681\controller)
        // Java目录分隔符需转换为文件系统格式(com.atd681 -> com/atd681)
        String controllerDir = controllerPackage.replaceAll("\\.", "/");
        String controllerPath = getClass().getClassLoader().getResource(controllerDir).getPath();
    
        // 遍历控制器目录下的所有CLASS
        for (File controller : new File(controllerPath).listFiles()) {
    
            String className = controller.getName().replaceAll("\\.class", ""); // 控制器类名称
            Class<?> clazz = Class.forName(controllerPackage + "." + className); // 加载控制器类
    
            // 遍历控制器类的所有方法,将每个方法和处理的URL做映射
            for (Method method : clazz.getMethods()) {
    
                // 只处理有@RequestMapping注解的方法
                if (!method.isAnnotationPresent(RequestMapping.class)) {
                    continue;
                }
    
                RequestMapping rm = method.getAnnotation(RequestMapping.class);
    
                // 同一URL可能以GET或POST提交, URL和HTTP方法(GET/POST)才能确定是相同的请求
                // 将URL和HTTP方法作为KEY, 使用统一方法生成KEY便于在分发时准确的获取对应的方法
                String urlKey = wrapperKey(rm.url(), rm.method());
    
                // 当多个方法同时声明了相同的请求时, 在前端控制器分发时无法准确的找到对应方法
                if (urlMappings.containsKey(urlKey)) {
                    throw new Exception("URL不能重复, URL: " + rm.url());
                }
    
                // 保存URL及对应的方法
                urlMappings.put(urlKey, method);
    
            }
    
        }
    
    }
    复制代码
    • 多个方法配置了处理相同的URL后, 前端控制器在收到请求进行分发时将无法分辨本次请求应该由哪个方法处理, 因此不能有两个以上的方法声明处理同一URL, 加载控制器时需要进行URL重复性验证.

    • HTTP支持使用GET/POST等多种方式请求同一URL. 例: GET方式请求/add代表访问添加页, POST方式请求/add代表提交数据. 由于两次请求业务不同, 需要有两个方法分别处理. 因此需要有两个方法配置处理/add请求, 用HTTP Method区分(GET&POST). 在设置控制的方法urlMapping时需要使用URL+HTTP Method作为KEY. 在请求分发时也应该根据URL+HTTP Method做为KEY找到对应处理方法.

    封装统一的规则生成urlMapping的KEY, 在设置urlMapping和分发时使用相同的KEY.

    /**
     * 封装URL映射的KEY,在加添加映射和分发时使用相同的KEY
     * 
     * @param url
     * @param method
     * @return url|GET
     */
    private String wrapperKey(String url, String method) {
        return url.toLowerCase() + "|" + method.toUpperCase();
    }
    复制代码

    3.3.3. 配置前端控制器

    web.xml中配置前端控制器处理所有请求, 同时指定配置文件路径.

    <servlet>
        <servlet-name>mvc</servlet-name>
        <servlet-class>com.atd681.xc.ssm.framework.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>configLocation</param-name>
            <param-value>/com/atd681/xc/ssm/framework/mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>mvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    复制代码

    至此, 项目启动时会加载前端控制器, 加载时会将所有处理的方法和处理URL一一映射后保存.

    3.4. 请求分发

    J2EE中规定, Servlet处理请求时会执行service方法. 因此分发的逻辑需要写到service方法中. 在收到请求时根据请求的URL从urlMapping中找到对应的方法, 通过JAVA反射动态调用方法即可.

    通常, 项目中的控制器会调用业务逻辑层及DAO或其他接口, 调用过程中难免会出现未知的错误异常, 如果程序中没有处理异常信息, 那么这些异常将会返回至前端控制器中, 因此需要捕获这些异常, 便于用户统一处理. 也叫做全局异常处理机制.

    定义一个doService方法, 在该方法中执行分发的逻辑. 在service方法中调用doService执行分发并捕获其抛出的所有异常即可实现对异常的统一处理.

    /**
     * 处理所有请求
     */
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
        try {
            doService(req, resp);
        }
        catch (Exception e) {
            // 可以在这里捕获异常进行全局处理
            // 模拟Spring全局异常处理
            e.printStackTrace();
        }
    
    }
    复制代码

    请求分发在doService方法中.

    /**
     * 根据请求URL分发至控制器
     */
    private void doService(HttpServletRequest req, HttpServletResponse resp) throws Exception {
    
        // 获取当前请求对应的方法名称
        // urlMappings保存每个URL对应的处理方法, 详见init方法
        Method method = urlMappings.get(getMappingKey(req));
    
        // 未找到方法处理时跳转至全局错误页
        // 此处忽略该过程直接跑出异常
        if (method == null) {
            throw new RuntimeException("没有找到处理该请求的方法");
        }
    
        // 实例化控制器
        Object classInstance = method.getDeclaringClass().newInstance();
        // 通过Java反射调用控制器方法
        method.invoke(classInstance, new Object[] {});
    
    }
    复制代码
    • 分发时需要根据请求的URL及HTTP Method(GET&POST)作为KEY从urlMapping中找到对应的处理方法. 生成KEY的规则需要和设置urlMapping时生成的KEY的规则保持一致(统一调用wrapperKey方法).
    • 由于WEB应用服务器配置不同, 有些项目访问时需要在URL中加入项目名称. 当访问的URL中含有项目名称时, 无法找到对应的处理方法(urlMapping的URL不含有项目名称). 此时需要使用去除项目名称后的URL.
    /**
     * 根据Request取得访问地址对应的处理方法KEY
     * 
     * <pre>
     * 例: 请求路径/list, get请求. 对应的key为"/list|get"
     * 如果请求路径中含有项目名称,去掉项目名称, 例请求为:/demo/list,转换为/list
     * </pre>
     * 
     * @param req
     * @return
     */
    private String getMappingKey(HttpServletRequest req) {
        
        String httpMethod = req.getMethod().toUpperCase(); // HTTP Method(GET&POST)
        String httpUrl = req.getRequestURI().toLowerCase(); // 请求的URL
    
        // 由于WEB服务器配置不同, 有些项目访问时需要在URL中加入项目名称
        // 如果访问的URL中含有项目名称,将项目名称从URL中去除
        if (httpUrl.startsWith(req.getContextPath())) {
            httpUrl = httpUrl.replaceFirst(req.getContextPath(), "");
        }
    
        // 生成KEY的规则应和加载控制器时生成KEY的规则相同
        return wrapperKey(httpUrl, httpMethod);
        
    }
    复制代码

    至此, 我们已经实现了前端控制器的分发机制, 当有请求到达时前端控制器会将请求分发至对应的控制器方法处理.

    3.5. 请求参数绑定:

    上例的控制器方法中没有任何参数, 很多场景下控制器方法中需要参数(Request, Response, Model及请求参数等), SpringMVC提供了灵活的参数绑定机制.

    SpringMVC可以将请求中传递的参数绑定至控制器对应方法的参数中, 参数可以是基本数据类型, 也可以是自定义的Javabean. 以下面的URL为例, 请求时携带了3个参数.

    http://localhost/list?userName=zhangsan&age=30&gender=M

    控制器中使用3个参数来接收, 并且参数的位置可以任意

    @RequestMapping("/list")
    public String list(String userName, Integer age, String gender) {
    }
    复制代码
    
    @RequestMapping("/list")
    public String list(Integer age, String gender, String userName) {
    }
    复制代码

    当参数较多时可以定义一个Javabean来接收

    public class User {
    
        private String userName;
        private Integer age;
        private String gender;
    
        // Getter&Setter
        
    }
    复制代码
    @RequestMapping("/list")
    public String list(User user) {
        
    }
    复制代码

    如果你需要Request对象, 只需要在控制器方法的参数中添加即可. 并且参数的顺序仍然没有限制.

    @RequestMapping("/list")
    public String list(HttpServletRequest req, User user) {
    }
    复制代码
    @RequestMapping("/list")
    public String list(User user, HttpServletRequest req) {
    }
    复制代码

    下面分析一下SpringMVC如何做到如此灵活的参数绑定. 控制器方法中的参数可以分为几类:

    • 请求中自带的对象: 例如Request, Response, Session等.
    • 框架自定义的对象: 例如保存数据的ModelMap.
    • 接收请求参数的模型: 形式各样, 可以使用多个基本类型的参数或者Javabean接收.

    前端控制器在请求分发时已经可以获取到对应的控制器方法, 同样可以获取到方法的各个参数的类型. 每个参数都根据其类型, 找到对应的对象赋值即可. 我们需要根据参数类型判断参数属于哪一类:

    • 请求中自带的对象: 将J2EE中对象的对象赋值即可.
    • 框架自定义的对象: 实例化相应对象后进行赋值.
    • 非上述两类的参数全部认为是用来接收请求参数的.

    基于上面的分析来实现对控制器方法的参数进行动态绑定.

    // 自定义的ModelMap, 保存在此的数据便于在视图中使用
    Map<String, Object> model = new HashMap<String, Object>();
    
    // 处理请求的控制器方法参数类型数组
    Class<?>[] classes = method.getParameterTypes();
    // JAVA反射调用方法时需要传入参数的实例化对象数组
    Object[] methodParams = new Object[classes.length];
    
    // 遍历控制器方法的某个参数, 根据参数类型设置相应参数或其实例
    // 控制器方法的参数位置变化时, 此处设置的参数的实例化对象数组位置也随之变化
    for (int i = 0; i < classes.length; i++) {
        Class<?> paramClass = classes[i];
    
        if (paramClass.isAssignableFrom(HttpServletRequest.class)) {
            methodParams[i] = req; // 将J2EE的Request对象设置到参数
        }
        else if (paramClass.isAssignableFrom(HttpSession.class)) {
            methodParams[i] = req.getSession(); // 将J2EE的会话对象设置到参数
        }
        else if (paramClass.isAssignableFrom(Map.class)) {
            methodParams[i] = model; // 将自定义保存数据的Map设置到参数
        }
        else {
            // 其余的类型的参数为接收请求参数, 实例化该参数并将设置请求数据
            methodParams[i] = wrapperBean(req, paramClass);
        }
    
    }
    
    复制代码

    如果请求中的参数名称和Javabean的属性名称一致时, 通过JAVA反射机制将该参数的数据设置到Javabean的属性中.

    /**
     * 将请求中的参数分别设置到Javabean对应的属性中
     */
    @SuppressWarnings({ "unchecked" })
    private <T> T wrapperBean(HttpServletRequest req, Class<?> bean) {
    
        T beanInstance = null;
    
        // 实例化处理方法中从参数bean
        try {
            beanInstance = (T) bean.newInstance();
        }
        catch (Exception e) {
            throw new RuntimeException("请求参数映射出现错误", e);
        }
    
        // 请求中所有参数
        Set<String> keySet = req.getParameterMap().keySet();
    
        // 遍历请求中的参数将值设置到Javabean对应的属性中
        for (String reqParam : keySet) {
    
            try {
                Class<?> fieldType = bean.getDeclaredField(reqParam).getType(); // Bean中参数类型
                Object fieldValue = getRequestValue(req, reqParam, fieldType); // Bean中参数在请求中的值
                String fieldSetter = "set" +  reqParam.substring(0, 1).toUpperCase() + reqParam.substring(1); // Bean中参数的set方法
    
                // 使用属性的Setter方法将请求中的值设置到属性中
                bean.getMethod(fieldSetter, fieldType).invoke(beanInstance, fieldValue);
    
            }
            catch (Exception e) {
                // BEAN中没有请求中对应的参数属性时继续下一个参数处理
                // JAVA反射未找到类的属性时会抛出异常终止循环
            }
    
        }
    
        return beanInstance;
    
    }
    复制代码

    请求中取出的参数数据都是字符串类型的, 需要转换成Javabean相应属性的类型.

    /**
     * 将request属性值转换为对应JavaBean属性类型
     */
    private Object getRequestValue(HttpServletRequest req, String name, Class<?> type) {
        String value = req.getParameterValues(name)[0];
    
        if (Integer.class.isAssignableFrom(type)) {
            return Integer.valueOf(value);
        }
        else if (Long.class.isAssignableFrom(type)) {
            return Long.valueOf(value);
        }
        else if (BigDecimal.class.isAssignableFrom(type)) {
            return BigDecimal.valueOf(Long.valueOf(value));
        }
        else if (Date.class.isAssignableFrom(type)) {
            try {
                return new SimpleDateFormat().parse(value);
            }
            catch (ParseException e) {
                throw new RuntimeException("参数[name]格式不正确");
            }
        }
        return value;
    }
    复制代码

    4. 总结

    前端控制器编写完成, 运行项目:

    • 请求/list: 执行UserController.userList
    • 请求/detail: 执行UserController.userDetail
    • 请求list?userName=zhangsan&age=30&gender=M, 会将请求中的三个参数设置到UserController.userList方法的参数中.

    前端控制器的核心思想在于分发机制与参数绑定. 面向开发者屏蔽了J2EE的冗余代码, 提升了开发效率. 使得开发者可以更专注于业务开发. 在实现前端控制器的过程中大量运用了JAVA反射机制来实现动态处理. 对JAVA反射不太熟悉的开发者需要巩固一下相关知识点.

    下一篇将分析SpringMVC基于策略模式的视图解析机制.

    转载于:https://juejin.im/post/5ce405845188252dc603f596

    更多相关内容
  • 前端控制器 自定义前端控制器模式实现。 对于不需要使用任何框架的小型网络应用程序。 如何使用: 使用后缀“Ctrl”在包“Controller”中创建一个新类; 实现接口IAction; 在“WebContent”文件夹中创建与...
  • 设计模式-前端控制器

    2016-11-28 21:00:19
    ios平台中通过最简单的代码讲解前端控制器模式,可在博客http://blog.sina.com.cn/s/blog_161d504630102wxis.html中查看简单解释
  • 为使用户侧大量新能源能够充分地发挥作用并可对其有效管理,提出一种基于有源前端控制器的直流供电系统结构,在该结构中结合电力电子技术与高频隔离技术将配电降压变压器与低压AC-DC变换器一体化设计为有源前端控制...
  • 本文实例讲述了Zend Framework前端控制器用法。分享给大家供大家参考,具体如下: 常用方法 1.getInstance() 功能:用于获取前端控制器实例。 代码如下: <?php $front = Zend_Controller_Front::getInstance(); ...
  • 前端控制器

    千次阅读 2019-04-16 08:32:09
    1、前端控制器 负责统筹他前台与后台的数据交互的中枢,其本身并没有干什么事,只是接收一下请求和转发请求给其他程序处理,接收处理结的果,使后台程序有条不紊的进行。 当然,不是所有的用户访问,前端控制器...

    1、前端控制器 

    负责统筹他前台与后台的数据交互的中枢,其本身并没有干什么事,只是接收一下请求和转发请求给其他程序处理,接收处理结的果,使后台程序有条不紊的进行。

    当然,不是所有的用户访问,前端控制器都需要接收并转发处理,于是乎前端控制器还有一个功能就是,它拦截哪些访问的uri,需要指定。下面是前端控制器的配置

    2、配置SpringMVC的前端控制器DispatcherServlet

    2.1在web.xml中

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xmlns="http://java.sun.com/xml/ns/javaee"
    	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    	id="WebApp_ID" version="2.5">
    	<display-name>springmvc-mybaties</display-name>
    	<welcome-file-list>
    		<welcome-file>index.html</welcome-file>
    		<welcome-file>index.htm</welcome-file>
    		<welcome-file>index.jsp</welcome-file>
    		<welcome-file>default.html</welcome-file>
    		<welcome-file>default.htm</welcome-file>
    		<welcome-file>default.jsp</welcome-file>
    	</welcome-file-list>
    	 
    
    	<!-- 配置前端控制器 -->
    
    	<servlet>
    		<servlet-name>springmvc</servlet-name>
    		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    		<!-- init的 contextConfigLocation默认找该servlet的名称路径下的 servlet.xml -->
    		<init-param>
    			<param-name>contextConfigLocation</param-name>
    			<param-value>classpath:springmvc.xml</param-value>
    		</init-param>
    	</servlet>
    	<servlet-mapping>
    		<servlet-name>springmvc</servlet-name>
    		<!-- 1. /* 拦截所有 .jsp .js .png .css 等 不建议使用 
                 2. *.action *.do 拦截以do action结尾的请求 
    			EOR 3. / 拦截所有(不包括jsp) .js .png .css 强烈建议使用 前台 面向消费者 www.jd.com/search / 对静态资源放行 -->
    		<url-pattern>*.action</url-pattern>
    	</servlet-mapping>
    	
     
    </web-app>

     

     

     

     

     

    展开全文
  • 二、前端控制器(中心控制器) 1、什么是前端控制器 2、前端控制器原理 三、什么是SpringMVC 1、SpringMVC简介 2、SpringMVC和Struts2 四、SpringMVC第一个程序(配置版) 五、第一个SpringMVC程序小结 一、...

    目录

    在这里插入图片描述


    一、什么是MVC

    跳转到目录
    这里作简单介绍 具体看我之前的一篇博客: 三层架构和MVC思想

    • MVC是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件设计规范。
      是将业务逻辑、数据、显示分离的方法来组织代码。
    • MVC主要作用是降低了视图与业务逻辑间的双向偶合。
    • MVC不是一种设计模式,MVC是一种架构模式。当然不同的MVC存在差异。

    Model(模型):数据模型,提供要展示的数据,因此包含数据和行为,可以认为是领域模型或JavaBean组件(包含数据和行为),不过现在一般都分离开来:Value Object(数据Dao) 和 服务层(行为Service)。也就是模型提供了模型数据查询和模型数据的状态更新等功能,包括数据和业务。

    View(视图):负责进行模型的展示,一般就是我们见到的用户界面,客户想看到的东西。

    Controller(控制器):接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示。 也就是说控制器做了个调度员的工作。

    在这里插入图片描述

    最典型的MVC就是JSP + servlet + javabean的模式。

    在这里插入图片描述

    二、前端控制器(中心控制器)

    跳转到目录

    1、什么是前端控制器

    跳转到目录

    • Front Controller模式 要求在WEB应用系统的前端( Front )设置一个入口控制器( Controller ) ,是用来提供一个集中的请求处理机制,所有的请求都被发往该控制器统统一处理 ,然后把请求分发给各自相应的处理程序。
    • 一般用来做一个共同的处理,如权限检查,授权,日志记录等。因为前端控制的集中处理请求的能力,因此提高了可重用性和可拓展性。

    没有前端控制器前:
    在这里插入图片描述
    问题: 如果每次请求需要做相同的处理操作,此时每一个Servlet都要做重复的处理;

    2、前端控制器原理

    跳转到目录
    有了前端控制器
    在这里插入图片描述
    我们发现这个前端控制器,很像web里面的Filter过滤器;

    一般的, 我们把处理请求的对象称之为处理器 (Controller)

    • Apache习惯称之为Action ,如EmployeeAction.
    • Spring习惯称之为 Controller ,如EmployeeController.

    从这里能看出使用MVC框架必须在web.xml中配置前端控制器, 一般的要么是要Filter , 要么是Servlet.

    • Struts2基于Filter.
    • SpringMVC基于Servlet
      在这里插入图片描述

    三、什么是SpringMVC

    跳转到目录

    1、SpringMVC简介

    跳转到目录

    • Spring MVC是Spring Framework的一部分,是基于Java实现MVC的轻量级Web框架。

    官方文档:https://docs.spring.io/spring/docs/5.2.0.RELEASE/spring-framework-reference/web.html#spring-web

    Spring MVC的特点:

    • 轻量级,简单易学
    • 高效 , 基于请求响应的MVC框架
    • 与Spring兼容性好,无缝结合
    • 约定优于配置
    • 功能强大:RESTful、数据验证、格式化、本地化、主题等
    • 简洁灵活

    2、SpringMVC和Struts2

    跳转到目录
    SpringMVC和Struts2对比

    • Spring MVC的前端控制器是Servlet, 而Struts2是Fiter.
    • Spring MVC会稍微比Struts2 快些,Spring MVC是基于方法设计, 处理器是单例,而Sturts2 是基于类,每次发一次请求都会实例一个新的Action对象, Action是多例的。
    • Spring MVC更加简洁,开发效率Spring MVC比Struts2高,如支持JSR303校验,且处理AJAX请求更方便。
    • Struts2 的OGNL表达式使页面的开发效率相比Spring MVC更高些,但是Spring MVC也不差。

    总之SpringMVC目前的使用率已经远远超过了Struts2, Struts的漏洞比较多;

    四、SpringMVC第一个程序(配置版)

    跳转到目录

    1、导入Maven依赖
        <dependencies>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>5.1.9.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>servlet-api</artifactId>
                <version>2.5</version>
            </dependency>
            <dependency>
                <groupId>javax.servlet.jsp</groupId>
                <artifactId>jsp-api</artifactId>
                <version>2.2</version>
            </dependency>
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>jstl</artifactId>
                <version>1.2</version>
            </dependency>
        </dependencies>
    
    2、在 web.xml 中配置 前端控制器 DispatcherServlet(初始化Spring容器)
    <?xml version="1.0" encoding="UTF-8"?>
    <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">
    
            <!--配置前端控制器-->
            <!--1.注册DispatcherServlet-->
            <servlet>
                <servlet-name>springMVC</servlet-name>
                <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
                <!--关联一个springMVC的配置文件:【servlet-name】-servlet.xml-->
                <!--默认去WEB-INF目录中去寻找-->
                <!-- 默认寻找<servlet-name>元素文本内容-servlet.xml文件-->
                <init-param>
                    <param-name>contextConfigLocation</param-name>
                    <param-value>classpath:springmvc-servlet.xml</param-value>
                </init-param>
                <!--启动级别-1,在Tomcat启动时就初始化Spring容器-->
                <load-on-startup>1</load-on-startup>
            </servlet>
    
            <!--/ 匹配所有的请求;(不包括.jsp)-->
            <!--/* 匹配所有的请求;(包括.jsp)-->
            <servlet-mapping>
                <servlet-name>springMVC</servlet-name>
                <url-pattern>/</url-pattern>
            </servlet-mapping>
    
    </web-app>
    

    注意: <url-pattern>元素

    配置成 /,可支持 RESTfull风格,但会导致静态资源文件(jsp,css图片等)被拦截不能正常显示,但是可以通过配置来处理。推荐使用配置成 /*,可以请求到 controller中,但是跳转到jsp时会被拦截,不能渲染jsp视图,不使用,一般用于filter

    3、配置SpringMVC核心配置文件 springmvc-servlet.xml
    所以springmvc-servlet.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <!--配置1:处理器映射器-->
        <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
    
        <!--配置2:处理器适配器-->
        <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
    
        <!--配置3:视图解析器-->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="InternalResourceViewResolver">
            <!--前缀-->
            <property name="prefix" value="/WEB-INF/jsp/"/>
            <!--后缀-->
            <property name="suffix" value=".jsp"/>
        </bean>
    
        <!--配置4:处理器-->
        <!--通过处理器映射器在url中寻找id为hello的jsp-->
        <bean id="/hello" class="com.sunny.hello.HelloController"/>
        <bean id="/hello2" class="com.sunny.hello.HelloController2"/>
    </beans>
    

    对上面xml文件中处理器的解释

    3.1、在springmvc-servlet.xml文件中配置处理器映射器

    目的: 选择哪一个处理器(Controller)来处理当前请求
    BeanNameUrlHandlerMapping:
    根据请求的URL去寻找对应的bean, 根据bean的id/name

    eg: /hello 去匹配id或name为/hello的bean; 一般我们Controller都会加上@Controller注解; 根据请求的url寻找到@Controller标注的处理器bean; 就让其来处理当前请求

    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
    
    3.2、在springmvc-servlet.xml文件中配置处理器适配器

    目的: 调用处理器(Controller)的处理请求的方法 (RequestMapper)

    • 所有的适配器都实现了HandlerAdapter接口
    • 处理器(Controller)类必须实现org.springframework.web.servlet.mvc.Controller接口
    <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
    
    3.3、在springmvc-servlet.xml文件中配置视图解析器

    目的: 处理dispatcherServlet(前端控制器)给它的**ModelAndView

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="InternalResourceViewResolver">
        <!--前缀-->
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <!--后缀-->
        <property name="suffix" value=".jsp"/>
    </bean>
    
    3.4、编写我们要操作业务Controller ,要么实现Controller接口,要么增加注解;需要返回一个ModelAndView,装数据,封视图;
    public class HelloController implements Controller {
    
        /**
         * 模型视图类
         * 包含了要跳转的页面和共享的数据
         */
        public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
            //ModelAndView 模型和视图
            ModelAndView mv = new ModelAndView();
            //封装要跳转的视图,放在ModelAndView中
            mv.setViewName("welcome"); //: /WEB-INF/jsp/welcome.jsp
            //封装对象,放在ModelAndView中。Model
            mv.addObject("msg","HelloSpringMVC!");
            return mv;
        }
    }
    
    3.5、在springmvc-servlet.xml文件中配置处理器

    将自己写的类交给SpringIOC容器,注册bean (springmvc-servlet.xml文件中)
    注意: 在SpringMVC中Handler(框架)和Controller(自己)是同一个东西

    目的: 通过处理器映射器在url中寻找id为hello的jsp

    <!--通过处理器映射器在url中寻找id为hello的jsp-->
    <bean id="/hello" class="com.sunny.hello.HelloController"/>
    

    4、编写HelloController类

    public class HelloController implements Controller {
    
        /**
         * 模型视图类
         * 包含了要跳转的页面和共享的数据
         * @param request
         * @param response
         * @return
         * @throws Exception
         */
        public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
            //ModelAndView 模型和视图
            ModelAndView mv = new ModelAndView();
            //封装要跳转的视图,放在ModelAndView中
            mv.setViewName("welcome"); //: /WEB-INF/jsp/welcome.jsp
            //封装对象,放在ModelAndView中。Model
            mv.addObject("msg","HelloSpringMVC!");
            return mv;
        }
    }
    

    5、在WEB-INF下创建jsp目录下welcome.jsp

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
        ${msg}
    </body>
    </html>
    

    6、测试运行

    在这里插入图片描述

    如此, 第一个SpringMVC程序完成了

    可能遇到的问题:访问出现404,排查步骤:

    • 查看控制台输出,看一下是不是缺少了什么jar包。
    • 如果jar包存在,显示无法输出,就在IDEA的项目发布中,添加lib依赖!
    • 重启Tomcat 即可解决!

    这个错误很恶心,当项目发布,lib没有被添加,导致404,具体操作如下
    1、
    在这里插入图片描述
    2、
    在这里插入图片描述
    3、
    在这里插入图片描述
    4、这样就将jar包添加到Lib中了,此时重启tomcat即可!

    完整步骤:
    在这里插入图片描述

    五、第一个SpringMVC程序小结

    跳转到目录
    在这里插入图片描述
    在这里插入图片描述

    展开全文
  • 学习SpringMVC必知必会(1)~MVC 思想和前端控制器思想

    一、MVC 思想

    1.三层架构:

    Web 开发的最佳实践就是根据功能职责的不同,划分为控制层业务层持久层



    2.MVC 原理

    ✿ 控制层【MVC 模型】:

    是一种架构型的模式,本身不引入新功能,是控制层的一种思想,只是帮助我们将开发的结构组织的更加合理[MV分离] 使数据展示与模型分离、流程控制逻辑、【VC】业务逻辑调用与数据展示逻辑分离------>责任分离


    • Model(模型):数据模型,包含要展示的数据和业务功能。 【类:domain、dao、service】
    • View(视图):用户界面,在界面上显示模型数据。
    • Controller(控制器):起调度作用,接收用户请求、调用业务处理请求、共享模型数据并跳转界面。【servlet职责】



    3、MVC框架需要具备的功能【类比javaweb】

    • 设置请求编码

    • 接收请求参数

    • 输入校验

    • 参数类型转换

    • 把参数封装到对象

    • 设置共享数据

    • 文件上传

    • 文件下载

    • 控制界面跳转

    • 国际化处理

    • 自定义标签



    二、前端控制器

    1.什么是前端控制器

    Front Controller 模式要求在 WEB 应用系统的前端(Front)设置一个入口控制器(Controller),是用来提供一个集中的请求处理机制,所有的请求都被发往该控制器统一处理,然后把请求分发给各自相应的处理程序。

    ■ 简单说就是:处理所有请求共同的操作,再把请求分发给各自的处理器【后端控制器】。

    • 一般用来做一个共同的处理,如权限检查,授权,日志记录等。因为前端控制的集中处理请求的能力,因此提高了可重用性和可拓展性


    2、怎么保证所有请求都先经过前端过滤器呢?

    • 在javaweb:有过滤器Filter、servlet,都需要在web.xml 中做配置

    • 使用mvc框架,需要配置前端控制器:

      • Struts2 框架是基于Filter
      • SpringMVC 框架是基于Servlet
    展开全文
  • 前端控制器模式

    2019-03-09 16:42:04
    前端控制器模式(Front Controller Pattern)是用来提供一个集中的请求处理机制,所有的请求都将由一个单一的处理程序处理。该处理程序可以做认证/授权/记录日志,或者跟踪请求,然后把请求传给相应的处理程序。以下...
  • 前端控制器的作用

    2021-03-13 10:06:55
    前端控制器的作用 负责统筹他前台与后台的数据交互的中枢,其本身并没有干什么事,只是接收一下请求和转发请求给其他程序处理,接收处理结的果,使后台程序有条不紊的进行。 当然,不是所有的用户访问,前端控制器...
  • springmvc处理器及前端控制器介绍

    千次阅读 2017-08-22 10:12:37
    处理器(1)非注解的处理器映射和适配器处理器映射第一种非注解的映射另一种非注解的映射 class="com.amuxia.controller.ItemsController" /> <!-- 处理器映射 将bean的name作为url进行查找, ...
  • SpringMVC前端控制器的配置理解

    千次阅读 2020-08-29 15:45:46
    1.前端控制器(DispacherServlet):负责转发请求,接受用户的请求,申请处理后,将响应返回给客户 2.处理器映射器(HandlerMapping):前端控制器把请求转发给处理器映射器。处理器映射器根据请求中的URL,找到对应的...
  • SpringBoot前端控制器

    千次阅读 2020-02-18 14:13:24
    看了相关文章,有几篇不错的,mark一下。 首先是原始SpringMVC中的一个大致的框架以及流程图: https://segmentfault.com/a/1190000017528294 接下来是springboot中的源码分析: ...还有一个科普性质的发展史: ...
  • SpringMVC配置在web.xml中的前端控制器配置代码 web.xml <?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi=...
  • 把Spring MVC工作流实现完走了一遍后,从DispatcherServlet开始看它的具体实现,前端控制器(或叫分发器)作为整个流程的核心,依靠它完成HTTP请求的拦截和分发处理,翻看了...前端控制器DispatcherSe...
  • SpringMVC的核心就是DispatcherServlet,DispatcherServlet实质也是一个HttpServlet。DispatcherSevlet负责将请求分发,所有的请求都有经过它来统一分发。
  • 前端控制器的概述

    2019-06-29 15:16:00
    https://blog.csdn.net/weixin_36380516/article/details/77472148
  • springMVC前端控制器

    千次阅读 2018-07-13 10:56:13
    前端控制器处理过程:首先用户发起请求,请求到达SpringMVC的前端控制器(DispatcherServlet),前端控制器根据用户的url请求处理器映射器查找匹配该url的handle,并返回一个执行链,前端控制器再请求处理器适配器调用...
  • Spring MVC的前端控制器模式 spring mvc也是依赖servlet,所以spring mvc的请求处理是从一个servlet开始,这个servlet就是DispatcherServlet。 前端控制器模式(Front Controller Pattern)是用来提供一个集中的...
  • 主要为大家详细介绍了spring mvc DispatcherServlet之前端控制器架构,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • 什么是前端控制器模式(FronController)?

    千次阅读 2020-07-28 15:11:42
    提供请求统一处理接口,以便控制器验证与请求追踪 类图 实例 interface View { /** * 显示 */ void show (); } static class StudentView implements View { @Override public void.
  • 自己在做作业的时候碰到在jsp里导图片无法正常显示的情况,后来发现是自定义的interceptor对图片进行了拦截,所以无法...-- 配置Spring MVC前端核心控制器 --> <servlet> <servlet-name>Dispatc...
  • 在SpringMVC中我们在web.xml中配置前端控制器用来分发请求。如下: <!-- 配置前段控制器 --> <servlet> <servlet-name>springDispatcherServlet</servlet-name> <servlet-class>...
  • 1.前端控制器(DispatcherServlet) spring mvc也是依赖servlet,所以spring mvc的请求处理是从一个servlet开始,这个servlet就是DispatcherServlet。 前端控制器不处理请求,只做中转,它相当于一个中转站,所有...
  • 嵌入式Linux下HFC网管前端控制器的设计.pdf
  • 前端控制器和视图解析器

    千次阅读 2018-10-22 19:58:46
    前端控制器  通过源码我们可以了解DispatcherServlet的大体逻辑结构:  DispatcherServlet继承自FrameworkServlet,而FrameworkServlet是继承自HttpServletBean的,HttpServletBean又继承了HttpServlet。这是...
  • 手术机器臂前端控制器双重滤波的噪声抑制.pdf
  • 节点前端控制器 微服务测试的前端控制器

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 273,391
精华内容 109,356
关键字:

前端控制器

友情链接: DOMImplementationLS.rar